UOJ Logo zhouzixuan的博客

博客

bzoj 3534: [Sdoi2014]重建

2015-12-14 13:45:26 By zhouzixuan
【题目描述】:
    T国有N个城市,用若干双向道路连接。一对城市之间至多存在一条道路。
    在一次洪水之后,一些道路受损无法通行。虽然已经有人开始调查道路的损毁情况,但直到现在几乎没有消息传回。
    幸运的是,此前T国政府调查过每条道路的强度,现在他们希望只利用这些信息估计灾情。具体地,给定每条道路在洪水后仍能通行的概率,请计算仍能通行的道路恰有N-1条,且能联通所有城市的概率。
【题目解法】:
    我们考虑类比无向图的最小生成树算法,得到它的扩展:
    1.令g[i][j]表示(i,j)边的权值
    2.令g[i][i]=-sigma(g[i][j]) i!=j
    那么n-1阶主子式的相反数就是:每个生成树边权乘积之和

    但是这题如果直接这样做的话不能保证其他的边不再树上
    因此我们需要对每一种方案乘上其他边不在树上的概率
    也就是(引用自zky's blog):

    然后我们可以令原来的边权p[i][j]=p[i][j]/(1-p[i][j])
    最后答案再乘上所有(1-p[e])的乘积(注意双向边只能乘一次)
    证明的话:
    1.如果一个边e被选了,显然他对答案的贡献为p[e]/(1-p[e])*(1-p[e])=p[e]
    2.如果e不被选,那么它对答案的贡献就是(1-p[e])
    细节的话:如果一个1-p[e]等于0,把它当成eps计算即可
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N=55;
const double zero=1e-8;
int n; double a[N][N],ans,Ans;
inline long long getint()
{
    long long x=0; char c=getchar(); bool flag=false;
    while ((c!='-')&&((c<'0')||(c>'9'))) c=getchar();
    if (c=='-') flag=true,c=getchar();
    while ((c>='0')&&(c<='9')) x=x*10+(long long)(c-'0'),c=getchar();
    if (flag) return -x; else return x;
}
double calc(double x) {return max(x,zero);}
void init()
{
    n=getint(); Ans=1;
    for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) {scanf("%lf",&a[i][j]); if (i<j) Ans*=calc(1-a[i][j]);}
    for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) a[i][j]=a[i][j]/calc(1-a[i][j]);
    for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) if (i!=j) a[i][i]-=a[i][j];
    for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) a[i][j]=-a[i][j];
}
void solve()
{
    ans=1; n--;
    for (int i=1; i<=n; i++)
    {
        for (int j=i; j<=n; j++)
        {
            if (fabs(a[j][i])<zero) continue; if (i!=j) ans=-ans;
            for (int k=1; k<=n; k++) swap(a[i][k],a[j][k]); break;
        }
        if (fabs(a[i][i])<zero) continue; ans=ans*a[i][i];
        for (int j=i+1; j<=n; j++)
        {
            double tmp=a[j][i]/a[i][i];
            for (int k=1; k<=n; k++) a[j][k]-=tmp*a[i][k];
        }
    }
    ans=ans*Ans;
    printf("%.10lf\n",ans);
}
int main()
{
    init();
    solve();
    return 0;
}

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。