题目:
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.youkuaiyun.com/qq_43520313/article/details/109230716
https://blog.youkuaiyun.com/qq_43520313/article/details/109456811
https://blog.youkuaiyun.com/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(1≤ai≤ci)块,
∑
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(ai−1ci−1),令
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=1∑cif(i−1,j−l)l!(l−1ci−1)
再令
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=1∑jansi(j−in−i)(1)
解释就是,在真正分成
i
i
i个连通块不相邻的方案中,可以把某个连通块拆掉。
比如
a
n
s
2
ans_2
ans2中的方案,白白白|黑黑,变成,白白|白|黑黑就可以当成
g
3
g_3
g3中的方案。那么现在就还要
j
−
i
j-i
j−i个连通块,总共
n
n
n个木块,
a
n
s
i
ans_i
ansi表示已经分成
i
i
i段,那么就有
n
−
i
n-i
n−i个木块和前面颜色相同,从这些里面选
j
−
i
j-i
j−i个木块和前面的木块分开,就增加
j
−
i
j-i
j−i个连通块。
把式子变成
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=gj−i=1∑j−1ansi(j−in−i)
答案就是
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=1∑j[j−i=0](j−in−i)ansi=i=1∑jl=0∑j−i(−1)l(lj−i)(j−in−i)ansi(2)=i=1∑jl=0∑j−i(−1)l(j−i−ln−i)(n−jn−j+l)ansi=l=0∑j−1(−1)l(n−jn−j+l)i=1∑j−l(j−i−ln−i)ansi根据式(1)=l=0∑j−1(−1)l(n−jn−j+l)gj−l=l=1∑j(−1)j−l(n−jn−l)gl=l=1∑n(−1)j−lgl
就可以
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}
(lj−i)(j−in−i)=(j−in−i)(lj−i)=(ln−i)(j−i−ln−i−l)=(n−i−ln−i)(j−i−ln−i−l)=(j−i−ln−i)(n−jn−j+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;
}