天天看点

3424. 【NOIP2013模拟】粉刷匠DescriptionInputOutputSample InputSample OutputData ConstraintSolutionCode

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