题目大意
给定一个 n×m 的01矩阵,每次随机选择任意一个为 1 的格子并将其标记(只是标记,没有改变数值)。现在我们要使整个矩阵每一行、每一列都至少有一个格子被标记。求出期望的步数。
题目分析
50分算法
1≤n,m≤8
将行和列是否被标记压成二进制状态,然后记忆化搜索来计算期望。
时间复杂度
O(2n+mnm)
。
100分算法
换一种方式计算答案。
令
Pi
表示经过了
i
步还不能达到目标的概率,易得:
这个 Pi 看起来很难算,我们考虑枚举每一种行列标记二进制状态 S ,表示某些行/列式必须不被标记的,记在一次操作中其有一行/列被标记的概率为
但是如果直接算我们会算重(我们只是强制保证了 S 的这些行和列没有被标记,其他的行列状态没有限制),因此使用容斥原理:
观察到这里存在着等比数列和的极限。因为 1−p(S)∈[0,1) ,所以这里的等比数列和是收敛的。计算得
ans=∑|S|(−1)|S|+11p(S)=∑|S|(−1)|S|+1totcnt(S)
注意到这里一个状态可以由其选中的行列和的奇偶性、包括的黑点个数确定。
假设我们可以枚举行选与不选的二进制状态,然后统计出选择的行数的奇偶性 a ,已经包括的黑点个数
确定了行的状态后,考虑使用 dp 来计算状态总数,令 fi,j,k 表示第 i 列,已经选择的列总数的奇偶性为
现在我们有了 f ,就可以很方便地统计每一种状态对答案的贡献了。
注意不要把空的 S (不选任何一行和列)计算进来了……
这样的时间复杂度是
代码实现
50%
#include <iostream>
#include <cstdio>
using namespace std;
typedef long double db;
const int N=8;
const int D=N<<1;
const int S=1<<D;
int black[100][2];
bool solved[S];
int n,m,tot;
db E[S];
db calc(int sta)
{
if (solved[sta]) return E[sta];
if (sta==(1<<n+m)-1) return solved[sta]=1,E[sta]=0;
int same=0;
db ret=0;
for (int i=1,s;i<=tot;i++)
{
s=sta|(1<<black[i][0]-1)|(1<<black[i][1]+n-1);
if (s==sta) same++;
else ret+=(calc(s)+1.0)/tot;
}
(ret+=(1.0*same)/(1.0*tot))/=((1.0*(tot-same))/(1.0*tot));
return solved[sta]=1,E[sta]=ret;
}
int main()
{
freopen("refuse.in","r",stdin),freopen("refuse.out","w",stdout);
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
int x;
scanf("%d",&x);
if (x) black[++tot][0]=i,black[tot][1]=j;
}
calc(0);
printf("%.5lf\n",(double)E[0]);
fclose(stdin),fclose(stdout);
return 0;
}
100%
#include <algorithm>
#include <iostream>
#include <cstdio>
using namespace std;
const int N=14;
const int S=1<<N;
const int M=20;
const int B=200;
typedef long double db;
bool mat[M+5][M+5];
int f[M+5][2][B+5];
int col[M+5];
int n,m,s,p;
int bit[S];
db ans;
inline int lowbit(int x){return x&-x;}
void calc()
{
s=1<<n;
for (int s_=0;s_<s;s_++)
for (int x=s_;x;x-=lowbit(x)) bit[s_]++;
for (int i=1;i<=m;i++)
for (int j=1;j<=n;j++)
if (mat[j][i]) col[i]+=1<<j-1;
for (int sta=0,cnt;sta<s;sta++)
{
cnt=0;
for (int i=1;i<=n;i++)
if ((sta>>i-1)&1)
for (int j=1;j<=m;j++)
cnt+=mat[i][j];
for (int i=1;i<=m;i++)
for (int j=0;j<2;j++)
for (int k=0;k<=p-cnt;k++)
f[i][j][k]=0;
f[1][0][0]=1,f[1][1][bit[~sta&col[1]]]=1;
for (int i=1;i<m;i++)
for (int j=0;j<2;j++)
for (int k=0;k<=p-cnt;k++)
if (f[i][j][k]) f[i+1][j][k]+=f[i][j][k],f[i+1][j^1][k+bit[~sta&col[i+1]]]+=f[i][j][k];
for (int j=0;j<2;j++)
for (int k=0;k<=p-cnt;k++)
if (f[m][j][k]&&cnt+k)
ans+=(((bit[sta]+j)&1)?1:-1)*(1.0*p)/(1.0*(cnt+k))*f[m][j][k];
}
}
int main()
{
freopen("refuse.in","r",stdin),freopen("refuse.out","w",stdout);
scanf("%d%d",&n,&m),p=0;
if (n<=m)
for (int i=1;i<=n;i++)
for (int j=1,x;j<=m;j++)
scanf("%d",&x),p+=(mat[i][j]=x);
else
{
for (int i=1;i<=n;i++)
for (int j=1,x;j<=m;j++)
scanf("%d",&x),p+=(mat[j][i]=x);
swap(n,m);
}
calc();
printf("%.5lf\n",(double)ans);
fclose(stdin),fclose(stdout);
return 0;
}