【題解】LOJ #6488 數表【FWT】
阿新 • • 發佈:2020-12-25
題意
\(n\times m\) 的表格,一次操作可以將一行或一列在模 \(4\) 意義下加 \(1\),問任意次操作後表格內數之和的最小值。\(n\leq 10,m\leq 10^4\)
題解
行上的操作確定後,每一列都容易確定最優解。
記 \(c(i)\) 為列上狀態為 \(i\) 的列的個數,\(f(i)\) 為當一列為 \(i\) 時,做列操作後的最小和。二者做減法卷積便能得到每種行操作下的最優答案。
\(4\) 進位制的高維迴圈卷積類似於異或卷積,但在迴圈內要做長為 \(4\) DFT(暴力即可)
減法卷積在翻轉系數時怎麼做都能過,但大多數寫法得到的結果順序其實是亂的。
#include<bits/stdc++.h> using namespace std; const int N=21; int f[1<<N],c[1<<N],rt=86583718,mod=998244353,inv4=(mod*3ll+1)/4; int a[11][10001]; int p[4],q[4]; void dft(int tp){ int r=1; for(int i=0;i<4;i++){ q[i]=0; for(int j=3;j>=0;j--) q[i]=(q[i]*1ll*r+p[j])%mod; r=r*1ll*rt%mod; } if(tp==-1){ swap(q[1],q[3]); for(int i=0;i<4;i++)q[i]=q[i]*1ll*inv4%mod; } } void fwt(int *a,int lim,int tp){ for(int i=1;i<lim;i<<=2){ for(int j=0;j<lim;j+=i<<2){ for(int k=0;k<i;k++){ // cerr<<"| "<<j+k<<" "<<i<<endl; for(int l=0;l<4;l++)p[l]=a[i*l+j+k]; dft(tp); for(int l=0;l<4;l++)a[i*l+j+k]=q[l]; } } } } int main(){ int n,m; scanf("%d%d",&n,&m); for(int i=0;i<n;i++)for(int j=0;j<m;j++)scanf("%d",a[i]+j); for(int i=0;i<m;i++){ int s=0; for(int j=0;j<n;j++) s=(s<<2)|((4-a[j][i])&3); c[s]++; // if(s)c[(1<<(n*2))-s]++; // else c[0]++; } for(int i=0;i<(1<<n*2);i++){ int mn=0x7f7f7f7f; for(int j=0;j<4;j++){ int s=0; for(int k=0;k<n;k++) s+=((i>>(k*2))+j)&3; mn=min(s,mn); } f[i]=mn; } // for(int i=0;i<1<<(n*2);i++)cerr<<"! "<<c[i];cerr<<endl; fwt(f,1<<(n*2),1); fwt(c,1<<(n*2),1); // for(int i=0;i<1<<(n*2);i++)cerr<<"! "<<c[i];cerr<<endl; for(int i=0;i<1<<(n*2);i++)f[i]=f[i]*1ll*c[i]%mod; fwt(f,1<<(n*2),-1); int ans=0x7f7f7f7f; for(int i=0;i<1<<(n*2);i++)ans=min(ans,f[i]); cout<<ans; }