【题解】LOJ #6488 数表【FWT】

题目链接

题意

(n imes m) 的表格,一次操作可以将一行或一列在模 (4) 意义下加 (1),问任意次操作后表格内数之和的最小值。(nleq 10,mleq 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;
}

原文地址:https://www.cnblogs.com/wallbreaker5th/p/14190665.html