Description
赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =
现在,神庙里有N根排列成一直线的石柱,从1到N标号,长老要求用油漆将这些石柱重新粉刷一遍。赫克托有K桶颜色各不相同的油漆,第i桶油漆恰好可以粉刷Ci根石柱,并且,C1+C2+C3…CK=N(即粉刷N根石柱正好用完所有的油漆)。长老为了刁难赫克托,要求相邻的石柱颜色不能相同。
喜欢思考的赫克托不仅没有立刻开始粉刷,反而开始琢磨一些奇怪的问题,比如,一共有多少种粉刷的方案?
为了让赫克托尽快开始粉刷,请你尽快告诉他答案。
Input
第一行一个正整数T,表示测试数据组数
对于每一组测试数据数据:
第1行:一个正整数K
第2行:K个正整数,表示第i桶油漆可以粉刷的石柱个数,Ci。
Output
对于每组输入数据,输出一行一个整数,表示粉刷的方案数mod 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% N≤10, T≤5
50% N≤15, T≤5
80% K≤15,Ci≤5,T≤500
100% K≤15,Ci≤6,T≤2000
Solution
Paint
Solution1:
我们从前往后枚举每一个位置刷什么颜色。
考虑最暴力的做法,时间复杂度为O(N^K)。
优化:由于C<=6,我们试着将他们按着C分类。
设状态F[i][j][c0][c1][c2][c3][c4][c5][c6]记录的是在刷完第i个位置之后,有ci种颜料还能粉刷i次(c0+c1+c2+c3+c4+c5+c6=K),其中i这个位置用的是能粉刷j次的颜料中的一种(即粉刷之后这种颜料还能粉刷j-1次)。
转移:我们枚举i+1这个位置粉刷的颜色。设这种颜色在第x类(cx>0, x>0),分情况讨论:
x=j-1:因为j-1位置中有一种颜料是上一次粉刷过的,不能再选,故F[i+1][x][c0]...[c[x-1]+1][cx-1].....[c6] += F[i][j][c0][c1][c2][c3][c4][c5][c6] * (cx-1)
x!=j-1 :我们可以随意取一种填涂。F[i+1][x][c0]...[c[x-1]+1][cx-1].....[c6] += F[i][j][c0][c1][c2][c3][c4][c5][c6] * cx。
性质:由于c0+c1+c2+c3+c4+c5+c6=K,我们可以证明状态是很稀疏的(有一个严格的上届)。状态可以通过枚举求出。
Solution2:
我们从前往后枚举每种颜色的每一根粉刷的柱子放在哪个位置。
例如样例1 2 3,则往序列中从前往后放颜色分别为1,2,2,3,3,3的柱子。
递推:F[i][j][k]记录放了i根柱子之后,目前有j对相邻的柱子颜色相等,有k对相邻的柱子颜色相等,且颜色与柱子i的颜色相等。
转移:加入的柱子i+1共有i+1个位置可插入。
柱子i+1与柱子i的颜色不相同:那么有j个位置插入之后,就会使j减1,其余则不会。
F[i+1][j-1][0] += F[i][j][k] * j; F[i+1][j][0] += F[i][j][k] * (i+1-j)
柱子i+1与柱子i的颜色相同,柱子i的颜色为C且柱子i是颜色C中的第x根。
<1>很明显若把柱子i+1放在颜色为C的柱子左右或中间,j与k均会增加1。
现在计算这些位置的数量:若把颜色为C的x根柱子全部分开放,会产生x*2个位置(每根柱子的左右各一个),但现在有k对柱子相邻,位置则剩下x*2-k。
F[i+1][j+1][k+1] += F[i][j][k] * (x*2-k)
<2>放在颜色不为C的相同颜色的一对柱子之间:
F[i+1][j-1][k] += F[i][j][k] * (j-k)
<3>放在其余的位置:
F[i+1][j][k] += F[i][j][k] * (i+1 - (x*2-k) - (j-k)) 即为 F[i+1][j][k] += F[i][j][k] * (i+1-x*2+k*2-j)
最后的最后:因为每一种颜色都是顺次放置的,所以同一种颜色的柱子是有顺序的。所以最后要除去重复的情况(即除去每一种颜色的阶乘的积)。
即(1,2,2,3)就要除去(1!*2!*2!*3!)。
这一步用逆元来实现。
时间复杂度为O(N*N*C*T)。
Solution3:
由于Solution2中把一种能粉刷x次的颜色分成x步来做(由此出现了可耻的逆元),不是很和谐。
所以我们改进,每一步处理一种颜色。
设F[i][j]表示涂到第i种颜色,有j对相邻的柱子颜色相同。
记涂到第i种颜色之后一共有S块。
对于i+1这种颜色,能粉刷A次。
设把这种颜色分成k块,插进序列里面。
插进的位置中,有l个位置刚好插进在j对颜色相同的柱子中间(l<=j 且 l<=k)。
新状态的j:原有的j,新增的A-k,减少的l。
状态转移:
1 把颜色分成k块的方案数:C(A-1, k-1)
2 把块插进l个位置的方案数:C(j, l)
3 剩余块的处理方式:C(S+1-j, k-l)
转移方程如下:
F[i+1][j+A-k-l] += F[i][j] * C(j, l) * C(A-1, k-1) * C(S+1-j, k-l)
时间复杂度O(K*N*C*C*T)。(理论复杂度与上一解法一致)
(其实我不明白为什么Sol3比Sol2快呢)
Solution2、3优化:
我们看到T=2000这一条件极不和谐。
庆幸的是,将每个询问内的数字打乱顺序不影响答案。
我们可以将每个询问内部进行排序,再按字典序排序之后再处理,顺序处理。
处理询问i+1时,保留处理i时的f值。
若询问i与i+1的询问前x个数都相等,则对于x之前的f值都是相等的。
由于按字典序排序了,顺序处理会减少大多数的处理。
Code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define I int
#define F(i,a,b) for(I i=a;i<=b;i++)
#define mem(x,y) memset(x,y,sizeof(x))
#define N 16
#define M 1000000007
#define ll long long
using namespace std;
I T,t[N],n,sum;
ll f[N][100],g[110][110];
I read(){
I x=0;char ch=getchar();
while(ch<'0'||ch>'9'){ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x;
}
ll ks(ll x,ll y){
ll s=0;
while(x){
if(x&1) s=(s+y)%M;
y=(y*2)%M;
x>>=1;
}
return s;
}
I main(){
F(i,0,100) g[i][0]=1;
F(i,1,100){
F(j,1,i-1) g[i][j]=(g[i-1][j]+g[i-1][j-1])%M;
g[i][i]=1;
}
T=read();
while(T--){
n=read();
mem(f,0);
F(i,1,n) t[i]=read();
f[1][t[1]-1]=1;sum=0;
F(i,0,n-1){
sum+=t[i];
F(j,0,sum-1){
if(!f[i][j]) continue;
F(k,1,t[i+1]){
F(l,0,t[i+1]){
if(l>j||l>k) break;
ll ans=(f[i][j]*g[t[i+1]-1][k-1])%M;
ans=(ans*g[j][l])%M;
ans=(ans*g[sum+1-j][k-l])%M;
f[i+1][j+t[i+1]-k-l]=(f[i+1][j+t[i+1]-k-l]+ans)%M;
}
}
}
}
printf("%lld\n",f[n][0]);
}
return 0;
}
作者:zsjzliziyang
QQ:1634151125
转载及修改请注明
本文地址:https://blog.csdn.net/zsjzliziyang/article/details/98112491