[AGC002F]Leftmost Ball

Leftmost Ball

题解

首先一个序列白化后合法必须要求前缀白球数不小于前缀颜色数,否则肯定有个白球染的不是该种颜色的第一个球。
所以显然我们可以将球分成白球与有颜色的球往里放,有颜色的球重要的是第一个放置(未被染白的)的位置,这个位置会影响前缀颜色数,确定了第一个位置,其它球在后面乱填就好了,而每一个白球的位置都很重要,都会影响前缀和。

所以我们可以想到依次确定当前的第一个空位填那个球,记 d p i , j dp_{i,j} dpi,j表示已经填了 i i i个白球, j j j组有颜色球的方案数,显然转移就是下一个填白球还是有颜色的球,有转移方程式,
d p i , j = d p i − 1 , j + ( n k − i − ( j − 1 ) ( k − 1 ) − 1 k − 2 ) d p i , j − 1 ​ dp_{i,j}=dp_{i-1,j}+\binom{nk-i-(j-1)(k-1)-1}{k-2}dp_{i,j-1}​ dpi,j=dpi1,j+(k2nki(j1)(k1)1)dpi,j1白球显然是不需要区分颜色的,而后面有颜色的球可以先不考虑颜色,只要保证填的数量比白球少,将在后面的序列放置的方案数,也就是那个组合数乘上转移就行。最后再乘上 n ! n! n!表示有颜色的球第一个出现的颜色序列就行,毕竟不同颜色间是等价的。

顺着 d p dp dp一次就可以了。
时间复杂度 O ( n k ) O\left(nk\right) O(nk)

源码

#include<bits/stdc++.h>
using namespace std;
#define MAXN 2005
#define MAXM 4000005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
typedef unsigned long long uLL; 
typedef long double ld;
typedef pair<int,int> pii;
const int INF=0x3f3f3f3f;
const int mo=1e9+7;
const int mod=1e5+3;
const int inv2=5e8+4;
const int jzm=2333;
const int zero=2000;
const int n1=1000;
const int M=100000;
const int orG=3,ivG=332748118;
const long double Pi=acos(-1.0);
const double eps=1e-12;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*t*a%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,K,dp[MAXN][MAXN],fac[MAXM],inv[MAXM],ff[MAXM];
void init(){
	fac[0]=fac[1]=inv[0]=inv[1]=ff[1]=1;
	for(int i=2;i<=n*K;i++)
		fac[i]=1ll*i*fac[i-1]%mo,
		ff[i]=1ll*(mo-mo/i)*ff[mo%i]%mo,
		inv[i]=1ll*ff[i]*inv[i-1]%mo;
}
int C(int x,int y){
	if(x<0||y<0||x<y)return 0;
	return 1ll*fac[x]*inv[y]%mo*inv[x-y]%mo;
}
signed main(){
	read(n);read(K);init();dp[0][0]=1;
	if(K==1){puts("1");return 0;}
	for(int i=1;i<=n;i++)
		for(int j=0;j<=i;j++){
			if(j<i)Add(dp[i][j],dp[i-1][j],mo);
			if(j>0)Add(dp[i][j],1ll*C(n*K-i-(j-1)*(K-1)-1,K-2)*dp[i][j-1]%mo,mo);
		}
	printf("%lld\n",1ll*fac[n]*dp[n][n]%mo);
	return 0;
}

谢谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值