AtCoder Grand Contest 002 F - Leftmost Ball dp

7 篇文章 0 订阅

题意

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

分析

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

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

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

考虑 dp[i][j] d p [ 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) d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i ] [ j − 1 ] ( ( ( j − 1 ) k + i + 1 ) + ( k − 1 ) − 1 k − 1 )

代码

#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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值