一道求数学期望的dp,刚开始因为概率论没学好的缘故,连样例都看不懂……后来因为做了一道概率题,才明白了样例。做出来之后发现其实只是道水题……
先把连通分量都缩成一个点,然后就是简单的状压了。因为n到达30,数组开不下,所以我是用map实现状压的。
状态转移方程:dp[i][st]=(n-1)/(n-s)+sum{cnt[k]*dp[k][st^(1<<k)]/(n-s)}
k表示未访问过的节点,cnt[k]表示节点所代表的连通分量所含的原节点数,s表示当前已连的节点所含的原节点总数。
这题还有个trick,就是样例是错的,不是输出一位小数,而应该是6位浮点数。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <map>
using namespace std;
vector<int> g[35];
int vis[35],cnt[35];
int n,m,e;
map<int,double> d[35];
void dfs(int u)
{
vis[u]=1;
cnt[e]++;
for(int i=0;i<g[u].size();i++)
{
int v=g[u][i];
if(!vis[v]) dfs(v);
}
}
int bitcount(int x)
{
int sum=0;
for(int i=0;i<e;i++) if(x&(1<<i))
sum+=cnt[i];
return sum;
}
double dp(int u,int st)
{
if(d[u].count(st)) return d[u][st];
double &ans=d[u][st];
int bc=bitcount(st);
if(bc==n) return ans=0;
ans=1.0*(n-1)/(n-bc);
for(int i=0;i<e;i++)
if(!((1<<i)&st))
{
ans+=dp(i,st^(1<<i))*cnt[i]/(n-bc);
}
return ans;
}
int main()
{
freopen("in.txt","r",stdin);
int T,kase=1;
cin>>T;
while(T--)
{
cin>>n>>m;
for(int i=1;i<=n;i++) g[i].clear();
int u,v;
while(m--)
{
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
memset(vis,0,sizeof(vis));
memset(cnt,0,sizeof(cnt));
e=0;
for(int i=1;i<=n;i++) if(!vis[i])
{
dfs(i);e++;
}
for(int i=0;i<e;i++) d[i].clear();
double ans=dp(0,1);
printf("Case %d: %.6f\n",kase++,ans);
}
return 0;
}