题目
Description
赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =
现在,神庙里有
N
N
N根排列成一直线的石柱,从
1
1
1到
N
N
N标号,长老要求用油漆将这些石柱重新粉刷一遍。赫克托有
K
K
K桶颜色各不相同的油漆,第i桶油漆恰好可以粉刷
C
i
C_i
Ci根石柱,并且,
C
1
+
C
2
+
C
3
+
⋯
+
C
K
=
N
C_1+C_2+C_3+\cdots +C_K=N
C1+C2+C3+⋯+CK=N(即粉刷
N
N
N根石柱正好用完所有的油漆)。长老为了刁难赫克托,要求相邻的石柱颜色不能相同。
喜欢思考的赫克托不仅没有立刻开始粉刷,反而开始琢磨一些奇怪的问题,比如,一共有多少种粉刷的方案?为了让赫克托尽快开始粉刷,请你尽快告诉他答案。
Input
第一行一个正整数
T
T
T,表示测试数据组数
对于每一组测试数据数据:
第
1
1
1行:一个正整数
K
K
K;
第
2
2
2行:
K
K
K个正整数,表示第
i
i
i桶油漆可以粉刷的石柱个数,
C
i
C_i
Ci。
Output
对于每组输入数据,输出一行一个整数,表示粉刷的方案数模
1000000007
1000000007
1000000007。
Sample Input
3
3
1 2 3
5
2 2 2 2 2
10
1 1 2 2 3 3 4 4 5 5
Sample Output
10
39480
85937576
Data Constraint
30
%
30\%
30%
N
≤
10
,
T
≤
5
N≤10, T≤5
N≤10,T≤5
50 % 50\% 50% N ≤ 15 , T ≤ 5 N≤15, T≤5 N≤15,T≤5
80 % 80\% 80% K ≤ 15 , C i ≤ 5 , T ≤ 500 K≤15,C_i≤5,T≤500 K≤15,Ci≤5,T≤500
100 % 100\% 100% K ≤ 15 , C i ≤ 6 , T ≤ 2000 K≤15,C_i≤6,T≤2000 K≤15,Ci≤6,T≤2000
分析
(为了和组合数区分,题目中的
C
i
C_i
Ci更名为
A
i
A_i
Ai)
计数DP,先处理前缀和
S
i
=
∑
j
=
1
i
A
j
S_i=\sum\limits_{j=1}^{i}A_j
Si=j=1∑iAj,定义
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]:用前
i
i
i种颜色,刷前
S
i
S_i
Si个柱子,其中有
j
j
j(
0
≤
j
≤
S
i
−
1
0\leq j\leq S_i-1
0≤j≤Si−1)个柱子不合法(第
x
x
x(
1
≤
x
≤
S
i
−
1
1\leq x\leq S_i-1
1≤x≤Si−1)个柱子不合法,当且仅当第
x
x
x个柱子的颜色和第
x
+
1
x+1
x+1个柱子的颜色不同)的方案数。答案为
d
p
[
k
]
[
0
]
dp[k][0]
dp[k][0]。
刷表法:将第
i
+
1
i+1
i+1种颜色分段插入到
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]的涂法中。
枚举
k
k
k(
1
≤
k
≤
A
i
+
1
1\leq k\leq A_{i+1}
1≤k≤Ai+1),表示将
i
+
1
i+1
i+1种颜色被分成
k
k
k段;
枚举
t
t
t(
0
≤
t
≤
m
i
n
(
j
,
k
)
0\leq t\leq min(j,k)
0≤t≤min(j,k)),表示我们要用分成的
k
k
k段中的前
t
t
t段,处理掉
t
t
t个不合法的柱子(剩下的
k
−
t
k-t
k−t段就随便放到合法的柱子旁边)。
这样处理后
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]就变成了
d
p
[
i
+
1
]
[
j
−
t
+
(
A
i
+
1
−
k
)
]
dp[i+1][j-t+(A_{i+1}-k)]
dp[i+1][j−t+(Ai+1−k)],即处理掉了
t
t
t个不合法的,又多了
A
i
+
1
−
k
A_{i+1}-k
Ai+1−k个不合法的(每段的最后一个插入进去后是合法的,所以要减
k
k
k)。
- 把第 i + 1 i+1 i+1种颜色分成 k k k段的方案数很简单(隔板法): C A i − 1 k − 1 C_{A_i-1}^{\ k-1} CAi−1 k−1;
- 从 j j j段中选 t t t个: C j t C_j^t Cjt;
- 剩下的随便放: C S i + 1 − j k − t C_{S_i+1-j}^{k-t} CSi+1−jk−t(共有 S i + 1 S_i+1 Si+1个空,不能再放入不合法的柱子后面,所以减 j j j)。
于是转移方程:
d
p
[
i
+
1
]
[
j
−
t
+
(
A
i
+
1
−
k
)
]
=
d
p
[
i
]
[
j
]
×
C
A
i
−
1
k
−
1
×
C
S
i
+
1
−
j
k
−
t
×
C
j
t
dp[i+1][j-t+(A_{i+1}-k)]=dp[i][j]\times C_{A_i-1}^{\ k-1}\times C_{S_i+1-j}^{k-t}\times C_j^t
dp[i+1][j−t+(Ai+1−k)]=dp[i][j]×CAi−1 k−1×CSi+1−jk−t×Cjt
边界:
d
p
[
0
]
[
0
]
=
1
dp[0][0]=1
dp[0][0]=1
要从
i
=
0
i=0
i=0开始刷表。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define MAXC 6
#define MAXK 15
#define MAXN MAXK*MAXC
#define MOD 1000000007
int K;
int A[MAXK+5],S[MAXK+5];
int dp[MAXK+5][MAXN+5];
int C[MAXN+5][MAXN+5];
void Init(int N){//预处理组合数
C[0][0]=1;
for(int i=1;i<=N;i++){
C[i][0]=1;
for(int j=1;j<=N;j++)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
}
}
int main(){
int T;
scanf("%d",&T);
Init(MAXN);
while(T--){
scanf("%d",&K);
for(int i=1;i<=K;i++)
scanf("%d",&A[i]),S[i]=S[i-1]+A[i];
memset(dp,0,sizeof dp);
dp[0][0]=1;
for(int i=0;i<K;i++){//注意这里
for(int j=0;j<=S[i];j++){
for(int k=1;k<=A[i+1];k++){
for(int t=0;t<=min(k,j);t++){
dp[i+1][j-t+A[i+1]-k]=(dp[i+1][j-t+A[i+1]-k]+(long long)dp[i][j]*C[A[i+1]-1][k-1]%MOD*C[S[i]+1-j][k-t]%MOD*C[j][t]%MOD)%MOD;
}
}
}
}
printf("%d\n",dp[K][0]);
}
}