Codeforces 1530F. Bingo
一年一更任务达成√
题目链接:F - Bingo
题目大意:给定一个 \(n\times n(n\le 21)\) 表格,表格中每个元素有 \(p_{i,j}\) 的概率为 \(1\),否则为 \(0\)。求至少有一行或一列或一条对角线全为 \(1\) 的概率,其中对角线指两条主副对角线。
考虑一个 naive 的容斥想法,直接容斥有多少条(行/列/对角线)是不全为 \(1\) 的,并进行计算。
此时发现不全为 \(1\) 的计算很繁,于是考虑求原题的补集。即求所有(行/列/对角线)均不全为 \(1\)(或者说均存在 \(0\))的概率,那么容斥之后则是求钦定某些(行/列/对角线)均全为 \(1\) 的概率,这个概率可以非常直接地算出来:把所有被钦定为 \(1\) 的位置对应的 \(p_{i,j}\) 乘起来即可。
那么采用这种做法,复杂度则是 \(O(n^22^{2n})\) 级别的,显然无法接受。
考虑一个神奇的想法,把容斥计算拆成两部分。假设我们已经钦定了有哪些列或对角线是全为 \(1\) 的,观察下我们计算的是什么。
我们的式子本来是所有被钦定为 \(1\) 的位置的 \(p_{i,j}\) 之积,而现在在这一前提下,则变成我们要再次钦定有哪些行是全为 \(1\) 的,并计算 在第一轮钦定中变为 \(1\) 的 \(p_{i,j}\) 之积乘上(枚举第二轮钦定哪些行全为 \(1\),并计算容斥系数乘上在第二轮钦定中新成为 \(1\) 的 \(p_{i,j}\) 之积的和)。
而对于括号内的部分,对应式子的意义等同于求每一行均不全为 \(1\) 的概率。这时我们发现,这里的每一行相互之间是独立的,所以每行的贡献可以分开算,最后再乘起来就好。
于是我们考虑对每一行预处理在钦定了些列或对角线为 \(1\) 后,该行不全为 \(1\) 的概率。注意到钦定某些位置为一相当于修改对应的 \(p_{i,j}\) 使其变为 \(1\)。所以把对应行 \(p_{i,j}\) 的乘积直接除掉对应列集合的乘积即可,这玩意可以直接利用 lowbit 转移 \(O(2^n)\) 求出来,而对于对角线的部分直接枚举四种情况分别做就好。这样时间复杂度是 \(O(n2^n)\) 的,足以通过此题。
在实现时,可以先 \(2^2\) 枚举对角线的情况,再容斥列,这样实现起来会方便些。
#include<bits/stdc++.h>
using namespace std;
#define MOD 31607
#define N 21
int n,m,ans,a[N][N],b[N][N],f[N][1<<N],c[N],g[1<<N],k[1<<N],Lg[1<<N];
int lowbit(int x){return x&(-x);}
int sol()
{
int res=0;
for(int i=0;i<n;i++){
f[i][0]=1;
for(int j=1;j<=m;j++)
f[i][j]=f[i][j^lowbit(j)]*b[i][Lg[lowbit(j)]]%MOD;
for(int j=0;j<=m;j++)f[i][j]=(MOD+1-f[i][j])%MOD;
}
for(int j=0;j<=m;j++)
for(int i=n-2;i>=0;i--)
(f[i][j]*=f[i+1][j])%=MOD;
for(int i=0;i<=m;i++)
res=(res+f[0][m^i]*g[i]%MOD*k[i])%MOD;
return res;
}
int pre(int o1,int o2)
{
int K=1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
b[i][j]=a[i][j];
if(o1)for(int i=0;i<n;i++)(K*=b[i][i])%=MOD,b[i][i]=1;
if(o2)for(int i=0;i<n;i++)(K*=b[i][n-i-1])%=MOD,b[i][n-i-1]=1;
for(int i=0;i<n;i++)c[i]=1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
c[j]=c[j]*b[i][j]%MOD;
g[0]=1;
for(int i=1;i<=m;i++)
g[i]=g[i^lowbit(i)]*c[Lg[lowbit(i)]]%MOD;
return K*sol()%MOD;
}
int main()
{
k[0]=1;
for(int i=0,o=1;i<N;i++,o<<=1)Lg[o]=i;
for(int i=1;i<(1<<N);i++)k[i]=MOD-k[i^lowbit(i)];
scanf("%d",&n),m=(1<<n)-1;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++){
scanf("%d",&a[i][j]);
(a[i][j]*=3973)%=MOD;
}
for(int i=0;i<2;i++)
for(int j=0;j<2;j++)
ans=(ans+(i^j?MOD-1:1)*pre(i,j))%MOD;
printf("%d\n",(MOD+1-ans)%MOD);
}