描述
题解
很神的一个期望状压树型 DP 。
第一步:进行缩点,因为有的点本身就是联通的,所以缩成一个点 x ,并用 cnt[x] 表示这个缩点所拥有的城市个数;
第二步:设 dp[u][st] 表示目前在 u 点的一个城市经过了 st 状态的缩点,还需要多少天才能消灭妖怪使满足道路通畅,因为这里 1≤N≤30 ,比较大,直接开数组没法开,并且没法使用滚动数组, 所以使用 map 存储。
现在来分析一下状态转移关系,设 n 为当前经过了 st 状态的城市个数,那么先考虑搞定这 n 个点所需要的时间,平均需要走 N−1N−n 次才能搞定这些城市;当然此时还没有考虑完毕,还有树型部分的情况,那就是没有访问过的节点,走到节点 i 的概率为 cnt[i]N−n,若走到 i 节点,还平均需要 dp[i][st | (1<<i)],则
dp[u][st]=N−1N−n+∑st & (1 << i) == 0cnt[i]N−n∗dp[i][st | (1<<i)]
这个状态转移部分着实不好想,期望很玄……头疼。
保存六位小数……
代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <map>
#define INF 0x3f3f3f3f
#define MAXN 32
#define MAXM 500
using namespace std;
int T, N, M, tot;
int vis[MAXN];
int cnt[MAXN];
int mp_[MAXN][MAXN];
map<int, double> dp[MAXM];
void dfs(int x)
{
vis[x] = 1;
cnt[tot]++;
for (int i = 1; i <= N; i++)
{
if (mp_[x][i] && !vis[i])
{
dfs(i);
}
}
}
int num(int x)
{
int ret = 0;
for (int i = 0; i < tot; i++)
{
if (x & (1 << i))
{
ret += cnt[i];
}
}
return ret;
}
double get_dp(int u, int st)
{
if (dp[u].count(st))
{
return dp[u][st];
}
double &ans = dp[u][st];
int n = num(st);
if (n == N)
{
return ans = 0;
}
ans = 1.0 * (N - 1) / (N - n);
for (int i = 0; i < tot; i++)
{
if (!(st & (1 << i)))
{
ans += get_dp(i, st | (1 << i)) * cnt[i] / (N - n);
}
}
return ans;
}
int main()
{
scanf("%d", &T);
for (int ce = 1; ce <= T; ce++)
{
scanf("%d%d", &N, &M);
memset(mp_, 0, sizeof(mp_));
memset(vis, 0, sizeof(vis));
memset(cnt, 0, sizeof(cnt));
int u, v;
while (M--)
{
scanf("%d%d", &u, &v);
mp_[u][v] = mp_[v][u] = 1;
}
// 缩点
tot = 0;
for (int i = 1; i <= N; i++)
{
if (!vis[i])
{
dfs(i);
tot++;
}
}
for (int i = 0; i < N; i++)
{
dp[i].clear();
}
printf("Case %d: %.6lf\n", ce, get_dp(0, 1));
}
return 0;
}