[JZOJ4844]抗拒黄泉/[Topcoder SRM583 Hard]

题目大意

给定一个 n×m 的01矩阵,每次随机选择任意一个为 1 的格子并将其标记(只是标记,没有改变数值)。现在我们要使整个矩阵每一行、每一列都至少有一个格子被标记。求出期望的步数。

1n,m20,1n×m200

题目分析

50分算法

1n,m8
将行和列是否被标记压成二进制状态,然后记忆化搜索来计算期望。
时间复杂度 O(2n+mnm)

100分算法

换一种方式计算答案。
Pi 表示经过了 i 步还不能达到目标的概率,易得:

ans=i=0Pi

这个 Pi 看起来很难算,我们考虑枚举每一种行列标记二进制状态 S ,表示某些行/列式必须不被标记的,记在一次操作中其有一行/列被标记的概率为 p(S) (令这些行/列包括了 cnt 个1点,总共有 tot 个1点,显然 p(S)=cnttot ),那么在操作 i 次之后其依然没有被标记的概率为 (1p(S))i
但是如果直接算我们会算重(我们只是强制保证了 S 的这些行和列没有被标记,其他的行列状态没有限制),因此使用容斥原理:
Pians=|S|(1)|S|+1(1p(S))i=i=0Pi=i=0|S|(1)|S|+1(1p(S))i

观察到这里存在着等比数列和的极限。因为 1p(S)[0,1) ,所以这里的等比数列和是收敛的。计算得
ans=|S|(1)|S|+11p(S)=|S|(1)|S|+1totcnt(S)

注意到这里一个状态可以由其选中的行列和的奇偶性、包括的黑点个数确定。
假设我们可以枚举行选与不选的二进制状态,然后统计出选择的行数的奇偶性 a ,已经包括的黑点个数 b
确定了行的状态后,考虑使用 dp 来计算状态总数,令 fi,j,k 表示第 i 列,已经选择的列总数的奇偶性为 j ,已经被包括的黑点数为 k (这里不包括那些在 b 里已经计算的),显然这个很容易递推出来。
现在我们有了 f ,就可以很方便地统计每一种状态对答案的贡献了。
ans+=j,kfm,j,k×totk+b×(1)j+a+1

注意不要把空的 S (不选任何一行和列)计算进来了……
这样的时间复杂度是 O(2nnm2) 的(如果你处理得好的话)。可是 n 可能达到 20 。但是注意到 n×m200 ,那么 min{n,m} 显然是小于等于 14 的。如果 n>m ,我们就交换一下行列,转置一下矩阵就好了~

代码实现

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;
}

你可能感兴趣的:(动态规划,OI,概率与期望,计数问题,等比数列求和)