着色方案

题目:
https://ac.nowcoder.com/acm/problem/14604

n n n个木块,从左到右排成一排,你有 k k k种颜色,每种颜色的可以涂 c i c_i ci个木块,所有的颜色正好够涂所有的木块,即 c 1 + c 2 + . . . + c k = n c_1+c_2+...+c_k=n c1+c2+...+ck=n。涂色时要求任意两块相邻木块不能同色,请统计出不同着色方案的总数。两种着色方案是不同的当且仅当两种方案至少有一个位置的木块颜色是不同的。

思路:
参考题目
https://blog.csdn.net/qq_43520313/article/details/109230716
https://blog.csdn.net/qq_43520313/article/details/109456811
https://blog.csdn.net/ezoilearner/article/details/84704313

现在就是假设总共分成了 j j j个连通块,假设第 i i i种颜色分成 a i ( 1 ≤ a i ≤ c i ) a_i(1\le a_i\le c_i) ai(1aici)块, ∑ i a i = j \sum_{i}a_i=j iai=j。则方案数为 j ! ∏ i a i ! ∏ i ( c i − 1 a i − 1 ) \frac{j!}{\prod_{i}a_i!}\prod_{i}{c_i-1\choose a_i-1} iai!j!i(ai1ci1),令 f ( i , j ) f(i,j) f(i,j)表示前 i i i种颜色分成 j j j段的方案数除以 j ! j! j!
f ( i , j ) = ∑ l = 1 c i f ( i − 1 , j − l ) ( c i − 1 l − 1 ) l ! f(i,j)=\sum_{l=1}^{c_i}f(i-1,j-l)\frac{{c_i-1\choose l-1}}{l!} f(i,j)=l=1cif(i1,jl)l!(l1ci1)
再令 g j = f ( k , j ) ∗ j ! g_j=f(k,j)*j! gj=f(k,j)j!,这就是分成 j j j段的方案数,但是这里没有考虑同种颜色的连通块可能会相邻的情况。也就是说实际的连通块可能小于 j j j(两个颜色相同的连通块相邻会合并成一个),令 a n s j ans_j ansj表示真正分成 j j j个连通块的方案数,则
g j = ∑ i = 1 j a n s i ( n − i j − i ) ( 1 ) g_j=\sum_{i=1}^{j}ans_i{n-i\choose j-i}\quad (1) gj=i=1jansi(jini)(1)
解释就是,在真正分成 i i i个连通块不相邻的方案中,可以把某个连通块拆掉。
比如 a n s 2 ans_2 ans2中的方案,白白白|黑黑,变成,白白|白|黑黑就可以当成 g 3 g_3 g3中的方案。那么现在就还要 j − i j-i ji个连通块,总共 n n n个木块, a n s i ans_i ansi表示已经分成 i i i段,那么就有 n − i n-i ni个木块和前面颜色相同,从这些里面选 j − i j-i ji个木块和前面的木块分开,就增加 j − i j-i ji个连通块。
把式子变成
a n s j = g j − ∑ i = 1 j − 1 a n s i ( n − i j − i ) ans_j=g_j-\sum_{i=1}^{j-1}ans_i{n-i\choose j-i} ansj=gji=1j1ansi(jini)
答案就是 a n s n ans_n ansn(没有相邻的就是 n n n个连通块),可以直接暴力 O ( n 2 ) O(n^2) O(n2)算。或者二项式反演
a n s j = ∑ i = 1 j [ j − i = 0 ] ( n − i j − i ) a n s i = ∑ i = 1 j ∑ l = 0 j − i ( − 1 ) l ( j − i l ) ( n − i j − i ) a n s i ( 2 ) = ∑ i = 1 j ∑ l = 0 j − i ( − 1 ) l ( n − i j − i − l ) ( n − j + l n − j ) a n s i = ∑ l = 0 j − 1 ( − 1 ) l ( n − j + l n − j ) ∑ i = 1 j − l ( n − i j − i − l ) a n s i 根 据 式 ( 1 ) = ∑ l = 0 j − 1 ( − 1 ) l ( n − j + l n − j ) g j − l = ∑ l = 1 j ( − 1 ) j − l ( n − l n − j ) g l a n s n = ∑ l = 1 n ( − 1 ) j − l g l \begin{aligned} ans_j&=\sum_{i=1}^{j}[j-i=0]{n-i\choose j-i}ans_i\\ &=\sum_{i=1}^{j}\sum_{l=0}^{j-i}(-1)^l{j-i\choose l}{n-i\choose j-i}ans_i\quad (2)\\ &=\sum_{i=1}^{j}\sum_{l=0}^{j-i}(-1)^l{n-i\choose j-i-l}{n-j+l\choose n-j}ans_i\\ &=\sum_{l=0}^{j-1}(-1)^l{n-j+l\choose n-j}\sum_{i=1}^{j-l}{n-i\choose j-i-l}ans_i\quad 根据式(1)\\ &=\sum_{l=0}^{j-1}(-1)^l{n-j+l\choose n-j}g_{j-l}\\ &=\sum_{l=1}^{j}(-1)^{j-l}{n-l\choose n-j}g_l\\ ans_n&=\sum_{l=1}^{n}(-1)^{j-l}g_l \end{aligned} ansjansn=i=1j[ji=0](jini)ansi=i=1jl=0ji(1)l(lji)(jini)ansi(2)=i=1jl=0ji(1)l(jilni)(njnj+l)ansi=l=0j1(1)l(njnj+l)i=1jl(jilni)ansi(1)=l=0j1(1)l(njnj+l)gjl=l=1j(1)jl(njnl)gl=l=1n(1)jlgl
就可以 O ( n ) O(n) O(n)算,不过没必要反正前面 d p dp dp已经是 O ( n 3 ) O(n^3) O(n3)
( 2 ) (2) (2)的化简
( j − i l ) ( n − i j − i ) = ( n − i j − i ) ( j − i l ) = ( n − i l ) ( n − i − l j − i − l ) = ( n − i n − i − l ) ( n − i − l j − i − l ) = ( n − i j − i − l ) ( n − j + l n − j ) \begin{aligned} {j-i\choose l}{n-i\choose j-i}&={n-i\choose j-i}{j-i\choose l}\\ &={n-i\choose l}{n-i-l\choose j-i-l}\\ &={n-i\choose n-i-l}{n-i-l\choose j-i-l}\\ &={n-i\choose j-i-l}{n-j+l\choose n-j} \end{aligned} (lji)(jini)=(jini)(lji)=(lni)(jilnil)=(nilni)(jilnil)=(jilni)(njnj+l)

#include<bits/stdc++.h>
#define mod 1000000007
#define ll long long
using namespace std;
const int N=1009,M=1009;
int T,k,Sum;
ll c[N],p[M],p1[M],f[N][N],ans[N];
ll qpow(ll a,ll b) {ll res=1;a%=mod;while(b) {if(b&1)res=res*a%mod;a=a*a%mod;b>>=1;}return res;}
ll cal(int a,int b) {if(a<0||b<0||a<b)return 0;return p[a]*p1[b]%mod*p1[a-b]%mod;}
int main() {
    p[0]=1;
    for(int i=1; i<M; i++)
        p[i]=p[i-1]*i%mod;
    p1[M-1]=qpow(p[M-1],mod-2);
    for(int i=M-2; i>=0; i--)
        p1[i]=p1[i+1]*(i+1)%mod;
    cin>>T;
    while(T--) {
        Sum=0;
        memset(f,0,sizeof(f));
        memset(ans,0,sizeof(ans));
        cin>>k;
        for(int i=1; i<=k; i++)
            cin>>c[i],Sum+=c[i];
        f[0][0]=1;
        for(int i=1; i<=k; i++)
            for(int j=0; j<=Sum; j++) {
                for(int l=1; l<=c[i]; l++)
                    if(l<=j)
                        f[i][j]=(f[i][j]+f[i-1][j-l]*cal(c[i]-1,l-1)%mod*p1[l]%mod)%mod;
            }
        for(int i=1; i<=Sum; i++)
            f[k][i]=f[k][i]*p[i]%mod;
        for(int i=1;i<=Sum;i++)
            ans[Sum]=(ans[Sum]+((Sum-i)&1?mod-1:1)*f[k][i]%mod)%mod;
        /*for(int i=1; i<=Sum; i++) {
            ll res=0;
            for(int j=1; j<i; j++)
                res=(res+ans[j]*cal(Sum-j,i-j)%mod)%mod;
            ans[i]=(f[k][i]-res+mod)%mod;
        }*/
        printf("%lld\n",ans[Sum]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值