【弱省胡策】Round #5 Count

【弱省胡策】Round #5 Count

img

太神仙了。

\(DP\)做法

\(f_{n,m,d,k}\)表示\(n*m\)的矩阵,填入第\(k\)个颜色,并且第\(k\)个颜色最少的一列上有\(d\)个块染了\(k\)颜色。
\[ \displaystyle f_{n,m,d,k}=\sum_{i=1}^nC_n^i\cdot (C_m^d)^i\cdot f_{i,m-d,0,k-1}\cdot f_{n-i,m,d+1,k} \]
边界条件特别烦,当\(n=1\)或者\(n==1\&\&m==0\)\(f\)的值为\(1\)

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 25
#define M 55

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

const ll mod=1e9+7;
int n,m,k;
ll f[N][M][M][105];
ll C[M][M];

ll ksm(ll t,ll x) {
    ll ans=1;
    for(;x;x>>=1,t=t*t%mod)
        if(x&1) ans=ans*t%mod;
    return ans;
}

int main() {
    for(int i=0;i<=50;i++)
        for(int j=0;j<=i;j++)
            C[i][j]=(!j||i==j)?1:(C[i-1][j-1]+C[i-1][j])%mod;
    n=Get(),m=Get(),k=Get();
    
    memset(f,0,sizeof(f));
    for(int i=0;i<=n;i++)
        for(int j=0;j<=m;j++)
            for(int d=0;d<=m+1;d++)
                for(int c=0;c<=k;c++)
                    if(!i||(i==1&&j==0)) 
                        f[i][j][d][c]=1;
                    
    
    for(int c=1;c<=k;c++) {
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=m;j++) {
                for(int d=j;d>=0;d--) {
                    for(int q=1;q<=i;q++) {
                        (f[i][j][d][c]+=C[i][q]*ksm(C[j][d],q)%mod*f[q][j-d][0][c-1]%mod*f[i-q][j][d+1][c])%=mod;
                    }
                    (f[i][j][d][c]+=f[i][j][d+1][c])%=mod;
                }
            }
        }
    }
    cout<<f[n][m][0][k];
    return 0;
}

容斥做法

参考Luogu P4463的容斥做法。

我们先定义两行同构为:这两行中每种颜色出现次数相同。

我们设\(f_n\)表示\(n\)\(m\)列且任意两行不同构的方案数。设\(s_i\)表示有\(i\)行矩形同构的方案数。

然后我们考虑加入第\(n+1\)行。我们枚举至少有\(i\)行与第\(n+1\)行同构的方案数为\(g_i\),则:
\[ g_i=s_i\cdot f_{n-i} \]
我们设恰好有\(i\)行与第\(n+1\)行矩形同构的方案数为\(h_i\),则:
\[ g_i=h_i+\binom{i+1}{i}h_{i+1} \]
因为剩下的\(n-i\)行两两不同构,所以最多有\(i+1\)行与第\(n+1\)行同构。

\(g_i\)的容斥系数为\(c_i\)。考虑\(h_i\)被计算的次数为\(c_i+\binom {i}{i-1}c_{i-1}\),所以
\[ c_i+\binom {i}{i-1}c_{i-1}=[i==0] \]
因为\(c_0=0\),所以我们就可以解出\(c_i=(-1)^i i!\)

关于计算\(s_i\)
\[ s_i=(\frac{m!}{\prod num_k!})^i \]
其中\(num_k\)表示第\(k\)种颜色出现的次数。

这个其实就可以枚举\(i\)之后做背包,然后显然可以用\(NTT\)优化,于是数据范围可以扩大。

(懒得写\(MTT\)就改了模数)

代码:

#include<bits/stdc++.h>
#define ll long long
#define N 205
#define M 205

using namespace std;
inline int Get() {int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9') {if(ch=='-') f=-1;ch=getchar();}while('0'<=ch&&ch<='9') {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}return x*f;}

const ll mod=998244353;
int n,m,k;
ll fac[M],ifac[M];

ll ksm(ll t,ll x) {
    ll ans=1;
    for(;x;x>>=1,t=t*t%mod)
        if(x&1) ans=ans*t%mod;
    return ans;
}
ll f[N],g[N];
ll C(int n,int m) {return fac[n]*ifac[m]%mod*ifac[n-m]%mod;}

ll NTT(ll *a,int d,int flag) {
    static ll G=3;
    static int rev[M<<2];
    int n=1<<d;
    for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<d-1);
    for(int i=0;i<n;i++) if(i<rev[i]) swap(a[i],a[rev[i]]);
    for(int s=1;s<=d;s++) {
        int len=1<<s,mid=len>>1;
        ll w=flag==1?ksm(G,(mod-1)/len):ksm(G,mod-1-(mod-1)/len);
        for(int i=0;i<n;i+=len) {
            ll t=1;
            for(int j=0;j<mid;j++,t=t*w%mod) {
                ll u=a[i+j],v=a[i+j+mid]*t%mod;
                a[i+j]=(u+v)%mod;
                a[i+j+mid]=(u-v+mod)%mod;
            }
        }
    }
    if(flag==-1) {
        ll inv=ksm(n,mod-2);
        for(int i=0;i<n;i++) a[i]=a[i]*inv%mod;
    }
}

void pre(int n) {
    fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
    ifac[n]=ksm(fac[n],mod-2);
    for(int i=n-1;i>=0;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
}

ll A[M<<2],ans[M<<2];
ll s[N][M];

void ksm(ll *A,ll *ans,int d,int x) {
    memset(ans,0,sizeof(ans));
    ans[0]=1;
    for(;x;x>>=1) {
        if(x&1) {
            NTT(ans,d,1),NTT(A,d,1);
            for(int i=0;i<1<<d;i++) ans[i]=ans[i]*A[i]%mod;
            NTT(ans,d,-1),NTT(A,d,-1);
            for(int i=1<<d-1;i<1<<d;i++) ans[i]=0;
        }
        NTT(A,d,1);
        for(int i=0;i<1<<d;i++) A[i]=A[i]*A[i]%mod;
        NTT(A,d,-1);
        for(int i=1<<d-1;i<1<<d;i++) A[i]=0;
    }
}

ll S[N];

int main() {
    n=Get(),m=Get(),k=Get();
    pre(N-5);
    int len=m*k;
    int d=ceil(log2(m<<1|1));
    for(int i=1;i<=n;i++) {
        memset(A,0,sizeof(A));
        for(int j=0;j<=m;j++) {
            A[j]=ksm(ifac[j],i);
        }
        memset(ans,0,sizeof(ans));
        ksm(A,ans,d,k);
        S[i]=ans[m]*ksm(fac[m],i)%mod;
    }
    
    f[0]=1;
    for(int i=1;i<=n;i++) {
        for(int j=0;j<i;j++) {
            g[j]=C(i-1,j)*f[i-j-1]%mod*S[j+1]%mod;
        }
        for(int j=i-1;j>=0;j--) g[j]=(g[j]-g[j+1]*(j+1)%mod+mod)%mod;
        f[i]=g[0];
    }
    cout<<f[n];
    
    return 0;
}

转载于:https://www.cnblogs.com/hchhch233/p/10473648.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值