poj 3156 用hash存状态的DP

NEERC2006的题,有点难度,好题。

题目大意:给一个有n个点,m条边的无向图,两点之间可以存在多条边。现在每次随机增加一条边,问使得全部点都连通需要增加多少次(期望值)。


求期望的题,由于期望的线性性,自然想到dp

首先是状态表示,f(s1,s2,s3,...sn)表示当前合并到了有n个连通分量,每个连通分量有s1,s2,s3,...sn个点,且s1<=s2<=s3...<=sn。(最小表示)

则有两种转移:一种是连边后将两个连通分量合并了,另一种则不是。

前者有sigma(si*sj)(1<=i,j<=n,i!=j)种,后者则有sigma(si*(si-1)/2)(1<=i<=n)种。

令前者发生的概率为p,后者为q。

动态规划的转移方程为:f = p*(1+r) + p*q*(2+r) + p*q^2*(3+r) ....其中r为p发生后,新状态的期望值。f的极限为p*(1/(1-q)+r)/(1-q)。

注意,为什么r为p发生后,新状态的期望值,这是因为我是bottom-up的,所以目标状态期望值为0。

至于存储状态,可以hash,也可以暴力存储(最多6000个状态)

#include <iostream>
#include <algorithm>
using namespace std;

const int pow=31,base=10222223,maxn=30;
int h[base][maxn],t[maxn],s[maxn],fa[maxn];
double f[base];
bool vis[base];
int n,m,nn;
int find(int x)
{
    if (fa[x]==x) return x;
    fa[x]=find(fa[x]);
    return fa[x];
}

int gethash(int t[])
{
    int i,ans=0;
    for (i=1;i<=t[0];i++)
        ans=(ans+(ans*pow+t[i])%base)%base;
    while (1)
    {
          if (!h[ans][0]) break;
          bool ff=1;
          for (i=0;i<=h[ans][0];i++)
              if (h[ans][i]!=t[i]) 
              {
                 ff=0;break;
              }
          if (ff) return ans;
          ans=(ans<base)?ans++:0;
    }
    for (i=0;i<=t[0];i++)
        h[ans][i]=t[i];
    return ans;
}
int cmp(int a,int b) {return a>b;}
double dp(int s)
{
     if (h[s][0]==1) return 0;
     if (vis[s]) return f[s];
     int i,j,k,ss,t[maxn];
     double p=0,q=0,r,ans=0;
     for (i=1;i<=h[s][0];i++)
         q+=h[s][i]*(h[s][i]-1)*1.0/2/nn;
     for (i=1;i<=h[s][0];i++)
         for (j=i+1;j<=h[s][0];j++)
         {
             p=h[s][i]*h[s][j]*1.0/nn;
             for (k=1;k<i;k++) t[k]=h[s][k];
             for (k=i+1;k<j;k++) t[k-1]=h[s][k];
             for (k=j+1;k<=h[s][0];k++) t[k-2]=h[s][k];
             t[0]=h[s][0]-1;t[h[s][0]-1]=h[s][i]+h[s][j];
             sort(t+1,t+h[s][0],cmp);
             ss=gethash(t);
             r=dp(ss);
             ans+=p*(1/(1-q)+r)/(1-q);
         }
     vis[s]=1;f[s]=ans;
     return ans;
}  

int main()
{
    freopen("pin.txt","r",stdin);
    freopen("pou.txt","w",stdout);
    cin >> n >> m; 
    int i,x,y;
    for (i=1;i<=n;i++) fa[i]=i,s[i]=1;
    for (i=0;i<m;i++)
    {
        cin >> x >> y;
        x=find(x);y=find(y);
        if (x==y) continue;
        fa[y]=x;
        s[x]=s[x]+s[y];s[y]=0;
    }
    nn=(n*(n-1))/2;
    for (i=1;i<=n;i++)
        if (s[i])
           t[++t[0]]=s[i];
    cout.precision(6);
    cout.setf(ios_base::floatfield,ios_base::fixed);
    cout << dp(gethash(t)) << endl;
    x=0;
    for (i=0;i<=base;i++)
        x+=(h[i][0]>0);
    cout << x;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值