AtCoder Grand Contest 002 F - Leftmost Ball dp

题意

给你n种颜色的球,编号为1到n,每种k个,然后把这些球放到一个序列上,每种颜色的最左边的球变成0,然后问有多少种序列?
1<=n,k<=2000

分析

首先考虑合法的满足什么性质,正着来的话,一定是0的数量大于等于颜色的数量,这个还满足充分性

我们还可以发现一个计数的技巧,就是我们可以把这些序号统一一下,最左边的i肯定放在最左边的i+1的左边,这样我们再乘上n!即可

然后我们来考虑正着dp,因为我们很难维护这个序列每个位置是长什么样子的,考虑不维护具体的位置,而是维护每种颜色的球

考虑dp[i][j]表示放了i个0的球,放了前j个颜色的方案数,但是这样的话你不知道你的颜色放在哪里比较好,不能保证最左边的i放在最左边的i+1的左边,插进去的时候且不能保证放在0的后面

考虑反着dp,那么就是0的数量小于等于颜色的数量
那么你放0的时候就可以保证颜色都在后面
放颜色的时候你只要保证留一个颜色在当前位,剩下的在后面随便插就好了,保证了编号大的颜色在后面

插入的时候就是一个可重复组合

具体是

dp[i][j]=dp[i1][j]+dp[i][j1](((j1)k+i+1)+(k1)1k1)

代码

#include <bits/stdc++.h>
#define ll long long
#define C(i,j) (fac[i] * inv[j] % Mod * inv[(i)-(j)] % Mod)
using namespace std;
const ll N = 10000010;
const ll Mod = 1e9+7;
inline ll read()
{
  ll p=0; ll f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

ll n,k; ll fac[N],inv[N];

ll f[2010][2010];
void upd(ll &x,ll y){x=(x+y)%Mod;}

int main()
{
  n = read(); k = read();

  fac[0] = 1; for(ll i=1;i<=n*k+n+k;i++) fac[i] = i;
  inv[0] = inv[1] = 1; for(ll i=2;i<=n*k+n+k;i++) inv[i] = (Mod - Mod / i) * inv[Mod%i] % Mod;
  for(ll i=1;i<=n*k+n+k;i++) fac[i] = fac[i-1] * i % Mod,inv[i] = inv[i-1] * inv[i] % Mod;

  f[0][0] = 1; k--; if(!k){printf("1\n"); return 0;}
  for(ll i=0;i<=n;i++)
  {
    for(ll j=i;j<=n;j++)
    {
      if(i) upd(f[i][j] , f[i-1][j]);
      if(j) upd(f[i][j] , f[i][j-1] * C(j*k+i-1,k-1) % Mod);
    }
  }

  return printf("%lld\n",f[n][n] * fac[n] % Mod),0;
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页