Codeforces 662C Binary Table(快速沃爾什變換)
阿新 • • 發佈:2018-08-18
void 一個 很好 class force sum char s tps sca
Problem
- 給定一個n(≤20)*m(≤100 000)的01矩陣,每次操作可以將一行或一列取反。
求最終1的最少個數。
Solution
- 前置技能:快速沃爾什變換(FWT)。
- 觀察到n較小,考慮\(O(2^n)\)枚舉每一行選或不選。
- 不妨設f(x)表示行的操作狀態為x時(我們可用一個二進制數表示狀態),經過各種列操作後所得到的最少的1的個數。
可以\(O(m)\)再掃一遍所有列。但顯然T飛了。
- 定義\(C_j\)表示有多少列的狀態為j;\(E_k\)表示對於某一列而言,若它經過各種行操作狀態變成了k,則它再經歷各種列操作後最少能得到的1的個數。
- 顯然,\(C_j\)我們對於每一列統計一下即可;而\(E_k\)
- 而且我們也可以得到一個較為顯然的式子:\(f(x)=\sum_{x \oplus j=k} C_j*E_k\)。這個式子的思路就是對於所有狀態為j的列,我們都通過狀態為x的行操作令其變成了\(x\oplus j=k\),然後再看看變成了k以後的答案。
- 可以暴力枚舉j,暴力轉移。但是這樣的復雜度是\(O(2^{2n})\)的。
- 註意到xor的特殊性:對於任何\(x\oplus y=z\),有\(y\oplus z=x\)。
- 因此,上式可化為:\(f(x)=\sum_{j\oplus k=x} C_j*E_k\)
- 觀察到這是一個卷積的形式,我們用FWT優化它。
時間復雜度:\(O(nm+2^nn)\)。
Code
#include <bits/stdc++.h> #define go(i,a,b) for(i=a;i<b;i++) using namespace std; typedef long long ll; const int N=21,M=1e5+1,S=1<<21; int i,j,n,m,s,tmp; char str[M]; bool a[N][M]; ll c[S],e[S],f[S],ans; void FWT(ll *tf) { for(int d=1;d<s;d<<=1) for(int m=d<<1,i=0;i<s;i+=m) for(int j=0;j<d;j++) { ll x=tf[i+j],y=tf[i+j+d]; tf[i+j]=x+y; tf[i+j+d]=x-y; } } void UFWT() { for(int d=1;d<s;d<<=1) for(int m=d<<1,i=0;i<s;i+=m) for(int j=0;j<d;j++) { ll x=f[i+j],y=f[i+j+d]; f[i+j]=x+y>>1; f[i+j+d]=x-y>>1; } } int main() { scanf("%d%d",&n,&m); go(i,0,n) { scanf("%s",str); go(j,0,m) a[i][j]=str[j]-48; } go(i,0,m) { s=0; go(j,0,n) s+=a[j][i]*(1<<j); c[s]++; } s=1<<n; go(i,0,s) { for(tmp=i; tmp; tmp>>=1) e[i]+=tmp&1; e[i]=min(e[i],n-e[i]); } FWT(c); FWT(e); go(i,0,s) f[i]=c[i]*e[i]; UFWT(); ans=n*m; go(i,0,s) ans=min(ans,f[i]); printf("%lld",ans); }
Codeforces 662C Binary Table(快速沃爾什變換)