【NOIP2013模拟】粉刷匠

文章目录

题目

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 N10,T5

50 % 50\% 50% N ≤ 15 , T ≤ 5 N≤15, T≤5 N15,T5

80 % 80\% 80% K ≤ 15 , C i ≤ 5 , T ≤ 500 K≤15,C_i≤5,T≤500 K15,Ci5,T500

100 % 100\% 100% K ≤ 15 , C i ≤ 6 , T ≤ 2000 K≤15,C_i≤6,T≤2000 K15,Ci6,T2000

分析

(为了和组合数区分,题目中的 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=1iAj,定义 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 0jSi1)个柱子不合法(第 x x x 1 ≤ x ≤ S i − 1 1\leq x\leq S_i-1 1xSi1)个柱子不合法,当且仅当第 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} 1kAi+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) 0tmin(j,k)),表示我们要用分成的 k k k段中的前 t t t段,处理掉 t t t个不合法的柱子(剩下的 k − t k-t kt段就随便放到合法的柱子旁边)。

这样处理后 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][jt+(Ai+1k)],即处理掉了 t t t个不合法的,又多了 A i + 1 − k A_{i+1}-k Ai+1k个不合法的(每段的最后一个插入进去后是合法的,所以要减 k k k)。

  • 把第 i + 1 i+1 i+1种颜色分成 k k k段的方案数很简单(隔板法): C A i − 1   k − 1 C_{A_i-1}^{\ k-1} CAi1 k1
  • 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+1jkt(共有 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][jt+(Ai+1k)]=dp[i][j]×CAi1 k1×CSi+1jkt×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]);
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值