一年一更任務達成√
題目連結: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);
}