CF468E Permanent 题解

考虑暴力状压 DP。

按行 DP,记录列哪些被选过,可以做到 \(O(2^kk^2)\)

注意到某一列扫完了之后这一列选没选过不重要,可以减少这里的状态。

简单优化一下,每次选择最少的一列,使这一列一定被减少一。

然后就可以过了,卡不满。

Code
const int N=1e5+5,mod=1e9+7;
int x[60],y[60],a[60],b[60],v[60],val[60][60],fac[N];
int ca[60],cb[60];
unordered_map<ll,int> f[60],g[60];
int main() {
    int n=read(),k=read();
    FOR(i,1,k) a[i]=x[i]=read(),b[i]=y[i]=read(),v[i]=(0ll+mod+read()-1)%mod;
    fac[0]=1;
    FOR(i,1,n) fac[i]=1ll*fac[i-1]*i%mod;
    sort(a+1,a+k+1),sort(b+1,b+k+1);
    int al=unique(a+1,a+k+1)-a-1,bl=unique(b+1,b+k+1)-b-1;
    FOR(i,1,al) FOR(j,1,bl) val[i][j]=-1;
    FOR(i,1,k) {
        x[i]=lower_bound(a+1,a+al+1,x[i])-a;
        y[i]=lower_bound(b+1,b+bl+1,y[i])-b;
        val[x[i]][y[i]]=v[i],ca[x[i]]++,cb[y[i]]++;
    }
    f[0][0]=1;
    while(1) {
        int id=0;
        FOR(i,1,bl) if(cb[i]&&(id==0||cb[i]<cb[id])) id=i;
        if(id==0) break;
        FOR(i,1,al) if(val[i][id]!=-1&&ca[i]) {
            ca[i]=0;
            vi S;
            ll all=((1ll<<k)-1)<<1;
            FOR(j,1,bl) if(val[i][j]!=-1) {
                S.pb(j),cb[j]--;
                if(cb[j]==0) all^=(1ll<<j);
            }
            FOR(j,0,k) for(auto u:f[j]) {
                g[j][u.fi&all]=(g[j][u.fi&all]+u.se)%mod;
                for(int l:S) if(!(u.fi&(1ll<<l)))
                    g[j+1][(u.fi|(1ll<<l))&all]=(g[j+1][(u.fi|(1ll<<l))&all]+
                                                1ll*u.se*val[i][l]%mod)%mod;
            }
            FOR(j,0,k) swap(f[j],g[j]),g[j].clear();
        }
    }
    int ans=0;
    FOR(i,0,k) for(auto u:f[i]) ans=(ans+1ll*fac[n-i]*u.se%mod)%mod;
    printf("%d\n",ans);
}
posted @ 2023-01-29 14:38  cnyz  阅读(26)  评论(0编辑  收藏  举报