[AGC002F] Leftmost Ball——DP、组合数

[AGC002F] Leftmost Ball

题目描述

给你 n n n 种颜色的球,每个球有 k k k 个,把这 n × k n\times k n×k 个球排成一排,把每一种颜色的最左边出现的球涂成白色(初始球不包含白色),求有多少种不同的颜色序列,答案对 1 0 9 + 7 10^9+7 109+7 取模。

——翻译来自洛谷

题解

每种颜色涂白第一个球之后,剩下就只有 k − 1 k-1 k1 个球。显然每种颜色要认领一个白球,这个白球必须在这种颜色的第一个球的前面。但是当遇到两个白球都在两种颜色的前面时,这两个白球被谁认领呢?交换认领白球显然是会算重的,所以我们不妨规定从左到右第 i i i 种出现的颜色认领第 i i i 个白球,那么就可以对所有白球进行编号。如果用箭头表示小球出现的相对先后关系,有如下关系图:
在这里插入图片描述
这下关系就清晰多了。如果我们从前往后考虑每种颜色的话,新白球和新颜色的第一个球能插入的位置都太多了,不好做DP,但是如果从后往前考虑,那么新颜色的第一个球必插入后一种颜色的第一个球的前面,新白球必插入最开头。而后一种颜色第一个球的前面必然全是白球,且最多 n − 1 n-1 n1 个。

所以我们可以设状态 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示从后往前考虑到第 i i i 种颜色,序列最开头有连续 j j j 个白球的方案数,有以下转移
d p [ i ] [ j ] = ∑ l = j − 1 i − 1 d p [ i − 1 ] [ l ] ∗ ( ( i − 1 ) ∗ k + 1 − j + k − 2 k − 2 ) dp[i][j]=\sum_{l=j-1}^{i-1}dp[i-1][l]*{(i-1)*k+1-j+k-2\choose k-2} dp[i][j]=l=j1i1dp[i1][l](k2(i1)k+1j+k2)
观察式子发现,后面的组合数系数和我们枚举的 l l l 无关,所以可以前缀和优化做到 O ( n 2 ) O(n^2) O(n2)

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
using namespace std;
const int MAXN=2505;
const ll INF=1e18;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[30],lpt;
inline void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}

inline ll ksm(ll a,ll b,ll mo){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
const ll MOD=1e9+7;
ll fac[6250005],inv[6250005];
inline void init(int n){
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=n;i++)fac[i]=fac[i-1]*i%MOD;
	inv[n]=ksm(fac[n],MOD-2,MOD);
	for(int i=n-1;i>1;i--)inv[i]=inv[i+1]*(i+1)%MOD;
}
inline ll C(int n,int m){
	if(m>n||m<0)return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int n,k;
ll f[MAXN][MAXN];
inline void ad(ll&a,ll b){a+=b;if(a>=MOD)a-=MOD;}
inline ll de(ll a,ll b){
	a-=b;if(a<0)a+=MOD;
	return a;
}
signed main()
{
	n=read(),k=read();
	if(k==1){print(1);return 0;}
	init(n*k);
	f[1][1]=1;
	for(int i=2;i<=n;i++){
		int a=(i-1)*k;
		for(int j=1;j<=i;j++)
			ad(f[i][j],C(a-j+k-1,k-2)*de(f[i-1][i-1],f[i-1][max(j-2,0)])%MOD);
		for(int j=1;j<=i;j++)ad(f[i][j],f[i][j-1]);
	}
	print(f[n][n]*fac[n]%MOD);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值