AGC002 F Leftmost Ball dp 组合数学

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/88560884

题目链接

题意:
给你 n n 种颜色的球,每个球有 k k 个,把这 n k n*k 个球排成一排,把每一种颜色的最左边出现的球涂成白色(初始球不包含白色),求有多少种不同的颜色序列,答案对 1 e 9 + 7 1e9+7 取模。 n , k < = 2000 n,k<=2000

题解:
说实话,我自己的想法和网上的题解挺不一样的,我自己感觉好像挺对的,但是有一些细节没有仔细想,感觉题解的写法比较好写就写了题解的写法。但是我还是在这里写一下我的想法,欢迎各位与我交流这种想法的正确性与复杂度。

我的想法是,考虑用组合数来算。首先我们假设没有每种颜色的第一个球会变成白球这个问题,那么应该怎么算。我的想法是,一开始,只有一种颜色的时候我们方案数是1,再加进第二种颜色之后,我们考虑插板法,相当于我们有 k + 1 k+1 个位置(新颜色可以放在队首队尾),要插 k k 个板,但是两个板是可以相邻的(两种新颜色的球是可以相邻的),于是可能要弄一个可重复的组合数,就是要先假设每一个板之间都先保证填上一个球然后再插板的那种组合数。再来新颜色的话我们也是能比较容易的算出有多少个板,我们就一种颜色一种颜色的加进去,最后复杂度瓶颈是组合数的预处理,也就是 O ( n k ) O(n*k)

那么我们现在考虑有了每种颜色的第一个会变成白色的条件的影响。我们加入一个颜色之后,我们只需要让这个白色球插在这个颜色其他球的前面就可以了。那么我们的一个想法是枚举第一个白色球插在什么位置,那么这个颜色剩下的 k 1 k-1 个球就要插在后面的那些位置,也就是后面的若干个位置插进 k 1 k-1 个板的方案数。我们仔细观察一下,会发现这个组合数比较独特,每一次都是选 k 1 k-1 个数,于是我们可以预处理所有 i i 个数选 k 1 k-1 个数的前缀和,确定出每一次的白球可以插在哪一个区间才能插完剩下的 k 1 k-1 个球,这样就能每次 O ( 1 ) O(1) 完成转移了。复杂度的话,在算答案的时候是 O ( n ) O(n) 的,复杂度瓶颈还是预处理组合数的 O ( n k ) O(n*k)

以上是我口胡的思路,正确性和复杂度未知,有兴趣的大佬可以与我交流一下或者去写一下。下面介绍正解的做法。

正解是用dp来做的。一个合法的方案,一定要在任意一个时刻都满足出现的白球数比出现的颜色数多。我们设 d p [ i ] [ j ] dp[i][j] 表示出现了 i i 个白球,出现了 j j 种除了白球之外的颜色的方案数。我们考虑转移,首先比较简单的一种是在原来的情况后面加了一个白球,也就是 d p [ i ] [ j ] + = d p [ i 1 ] [ j ] dp[i][j]+=dp[i-1][j] 。另一种情况是加进来一种新的颜色,为了能够计数,我们在加入这种颜色的时候就把所有的球都加进来。我们有 d p [ i ] [ j ] = d p [ i ] [ j 1 ] ( n j + 1 ) C n k i ( j 1 ) ( k 1 ) 1 k 2 dp[i][j]=dp[i][j-1]*(n-j+1)*C_{n*k-i-(j-1)*(k-1)-1}^{k-2} 解释一下这个式子,乘 ( n j + 1 ) (n-j+1) 是有这么多种可选的颜色,后面的组合数的含义是,我们要在剩余的位置中选出 k 2 k-2 个位置给这种颜色除了白色和放在这个位置的第一个球之外剩余的球数,剩余的位置就是总的位置数减去 i i 个白球,再减去已经放的 ( j 1 ) ( k 1 ) (j-1)*(k-1) 个位置,再去掉当前这个作为第一个位置的位置。初始值的话就是 d p [ i ] [ 0 ] = 1 dp[i][0]=1 ,也就是当前放了一堆白球,没放任何颜色的球的方案是 1 1

这样复杂度 O ( n k ) O(n*k)

代码:

#include <bits/stdc++.h>
using namespace std;

int n,k;
const long long mod=1e9+7;
long long dp[3010][3010],jie[4000010],ni[4000010];
inline long long ksm(long long x,long long y)
{
	long long res=1;
	while(y)
	{
		if(y&1)
		res=res*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return res;
}
int main()
{
	scanf("%d%d",&n,&k);
	if(k==1)
	{
		printf("1\n");
		return 0;
	}
	jie[0]=1;
	for(int i=1;i<=n*k;++i)
	jie[i]=jie[i-1]*i%mod;
	ni[n*k]=ksm(jie[n*k],mod-2);
	for(int i=n*k-1;i>=0;--i)
	ni[i]=ni[i+1]*(i+1)%mod;
	for(int i=1;i<=n;++i)
	dp[i][0]=1;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=i;++j)
		{
			int x=n*k-i-(j-1)*(k-1)-1;
			dp[i][j]=(dp[i-1][j]+dp[i][j-1]*(n-j+1)%mod*jie[x]%mod*ni[k-2]%mod*ni[x-k+2]%mod)%mod;
		}
	}
	printf("%lld\n",dp[n][n]);
	return 0;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/88560884
今日推荐