【搬自TC_SRM583 Hard】【JZOJ4844】抗拒黄泉 题解

题目大意

  有一个 n×mn \times m 的棋盘,每个格子要么是 0 要么是 1。每天等概率地选择一个 1 格子进行标记,若某时刻每行、每列都至少有一个格子被标记,则结束。求结束的期望天数。
  n,m20,  n×m200n,m \le 20,\ \ n \times m \le 200

【50%】n,m<=8

  设 f[s1][s2]f[s1][s2] 表示行状态为 s1s1、列状态为 s2s2,到达结束的期望天数。
  设总的 1 格子数量为 tottot,那么对于当前状态,有 t[s1][s2]t[s1][s2] 个 1 格子你选它不会改变当前状态,剩余 tott[s1][s2]tot-t[s1][s2] 个 1 格子则会改变当前状态。
  则 f[s1][s2]=t[s1][s2]tot(f[s1][s2]+1)+1tot(f[s1][s2]+1)f[s1][s2]=\frac{t[s1][s2]}{tot}(f[s1][s2]+1)+\sum\frac{1}{tot}(f[s1'][s2']+1),移一下项就解出来了。

【100%】n,m<=20, n*m<=200

  设 F(i)F(i) 表示经过 ii 次操作结束的概率,P(i)P(i) 表示经过 ii 次操作仍未结束的概率,则

ans=i=1F(i)×i=i=0(P(i)P(i+1))×(i+1)=i=0P(i)\begin{array}{rcl} ans&=&\sum_{i=1}^\infty F(i)×i\\ &=&\sum_{i=0}^\infty(P(i)-P(i+1))×(i+1)\\ &=&\sum_{i=0}^\infty P(i) \end{array}

  考虑如何计算 P(i)P(i)。既然未结束,则一定是漏掉了一些行或列。漏掉的意思是那些行和那些列所包含的 1 格子永远选不到。设行列状态为 SS,设选中该状态包含的 1 格子的概率为 p(S)p(S)(这个是小写),则容斥一下可得到

P(i)=S是奇数(1p(S))iS是偶数(1p(S))iP(i)=\sum_{|S|是奇数}(1-p(S))^i-\sum_{|S|是偶数}(1-p(S))^i

  所以

ans=i=0S是奇数(1p(S))iS是偶数(1p(S))ians=\sum_{i=0}^\infty \sum_{|S|是奇数}(1-p(S))^i-\sum_{|S|是偶数}(1-p(S))^i

  等比数列公式一下可得

ans=S是奇数1p(S)S是偶数1p(S)ans=\sum_{|S|是奇数}\frac{1}{p(S)}-\sum_{|S|是偶数}\frac{1}{p(S)}

  设总的 1 格子数量为 tottotSS包含的 1 格子数量为 t[S]t[S],则

ans=S是奇数tott(S)S是偶数tott(S)ans=\sum_{|S|是奇数}\frac{tot}{t(S)}-\sum_{|S|是偶数}\frac{tot}{t(S)}

  设使 t[S]=Xt[S]=X 的状态 SS 数量为 num[X]num[X](这里的数量是指容斥后的数量,即奇数减偶数),则

ans=X=1totnum[X]×totXans=\sum_{X=1}^{tot}num[X]×\frac{tot}{X}

  求 num[X]num[X] 则是一个简单状压dp,不赘述。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long LL;

const int maxn=25, maxN=2e4+5;

int n,m,N,mp[maxn][maxn],er[maxn],b0;

int w[maxN];
int get(int x)
{
int re=0;
for(; x; x-=(x&(-x))) re++;
return re;
}

int Sb[maxn],bl[maxn],br[maxn];
LL f[205][2],num[205];
int main()
{
fo(i,1,20) er[i]=1<<(i-1);

scanf("%d %d",&n,&m);
fo(i,1,n)
fo(j,1,m) scanf("%d",&mp[i][j]);
if (n>m) //n、m必有一个<=14,为了方便下面的状压dp,把小的作为行
{
fo(i,1,n)
fo(j,(i<=m)?i:1,m) swap(mp[i][j],mp[j][i]);
swap(n,m);
}
N=(1<<n)-1;

fo(j,1,m)
{
Sb[j]=Sb[j-1];
fo(i,1,n) if (mp[i][j])
{
bl[j]+=er[i];
br[i]++;
Sb[j]++;
b0++;
}
}
fo(s,0,N) w[s]=get(s);

fo(s,0,N)
{
int st=0;
fo(i,1,n) if (s&er[i])
{
if (!br[i]) {st=-1; break;}
st+=br[i];
}
if (st==-1) continue;
memset(f,0,sizeof(f));
f[st][0]=1;
fo(j,1,m) if (Sb[j]-Sb[j-1])
fd(i,min(st+Sb[j],b0),st)
{
LL g[2]={f[i][0],f[i][1]};
f[i][0]=f[i][1]=0;
fo(k,0,1)
{
f[i][k]+=g[k];
if (i+get(bl[j]-(s&bl[j]))<=b0)
f[i+w[bl[j]-(s&bl[j])]][k^1]+=g[k];
}
}
fo(i,st,b0)
{
fo(k,0,1) if ((get(s)^k)&1) num[i]+=f[i][k]; else num[i]-=f[i][k];
}
}

long double ans=0;
fo(x,1,b0) ans+=num[x]*(long double)b0/x;

printf("%.5f\n",(double)ans);
}