AGC002F 题解

AGC002F 题解

题意

给定 \(n\) 个球,每种球有 \(k\)个,会从每个颜色中产生 \(k\) 个白球,每个颜色从左到右的第一个球变成白球,问一共有多少种从左到右不同的排列方式 (颜色相同的球视为相同)。

分析

一眼计数,但是好像没有比较明显的 \(O(1)\) 算法。而且 dp 也没有什么比较明显的设计,最致命的在于:没有什么可以作为阶段和状态的量。

只能先手玩一下样例,发现 \(211122\)\(121122\) 在变 \(0\) 之后实际上都是 \(001122\) 。会发现实际上是算重导致的,但是如果按照题目给的生成流程进行思考,很难解决算重的情况。

下面就是从题解区学到的东西了。

Point1

一个合法的排列,任意一个前缀的 \(0\) 的数量一定大于等于已经出现的颜色数量,比如 \(001122\) 就是合法的,但是 \(112200\) 显然是不合法的,因为这不符合题目给的要求。

Point2

根据以上,我们可以设计出一个递推:定义 \(f_{i,j}\) 为往这 \(n\times k\) 个位置里面填了 \(i\)\(0\)\(j\) 种颜色的方案数。

个人认为这个东西其实算不上 dp ,因为没有明确的 “阶段” 。但是仍然可以做一些递推,考虑现在要填入的是 \(0\) 还是某种颜色。

无论如何,你得枚举一下填的位置,这样就免不了要去记录位置的状态(难道还要开个 \(n\times k\) 的二进制数吗 ?),不仅会爆掉,而且会带来算重的问题。

Point3

所以我们要想着改良一下,钦定以下,无论是填入 \(0\) 还是 某种颜色,第一个球都填到当前剩下的第一个空位里面(没有必要去考虑这个空位在哪里,在哪里都可以,但是我们只用计算出方案数即可。),就不用考虑去重了。

这个过程相当于,我现在已经有了某个排列,然后我按照一种唯一的填数方案去把这个排列还原。

所以转移方程就呼之欲出了。

\(f[i][j]\) 可以从 \(f[i-1][j]\) 转移,也可以从 \(f[i][j-1]\) 转移,其中后者需要乘上一个系数,因为这第 \(j\) 种颜色有 \(n-j+1\) 种颜色选择,并且有 \(k-1\) 个需要填的球,当我们把第一个球填到了第一个空位,还剩下 \(k-2\) 个球,直接放到 \(n\times k-i-(j-1)\times (k-1)-1\) 个位置里面,因此还需要乘上一个 \(C_{n\times k-i-(j-1)\times (k-1)-1}^{k-2}\) ,所以总的转移方程就是 :

\[f[i][j]=f[i-1][j]+f[i][j-1]\times(n-j+1)\times C_{n\times k-i-(j-1)\times (k-1)-1}^{k-2} \]

并且需要实时保证 \(j\le i\) ,边界条件是 \(f[i][0]=1\)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll MOD=1e9+7;
const int N=5e6+10;
ll invjc[N],jc[N];
ll n,k;
inline ll power(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1)ans=ans*a%MOD;
        a=a*a%MOD;
        b>>=1;
    }
    return ans%MOD;
}
inline void pre()
{
    cin>>n>>k;
    jc[0]=1;
    int MAX=n*k;
    for(int i=1;i<=MAX;++i)jc[i]=jc[i-1]*i%MOD;
    invjc[MAX]=power(jc[MAX],MOD-2);
    for(int i=MAX-1;i>=0;--i)invjc[i]=invjc[i+1]*(i+1)%MOD;
}
inline ll c(ll n,ll m){return jc[n]*invjc[m]%MOD*invjc[n-m]%MOD;}
ll dp[2010][2010];
signed main()
{
    pre();
    if(k==1)return cout<<1,0;
    for(int i=1;i<=n;++i)
    {
        dp[i][0]=1;
        for(int j=1;j<=i;++j)
        {
            dp[i][j]=(dp[i-1][j]+dp[i][j-1]*(n-(j-1))%MOD*c(n*k-i-1ll*(j-1)*(k-1)-1,k-2)%MOD)%MOD;
        }
    }
    cout<<dp[n][n];
    return 0;
}
/*
    1.从某一种情况的 “唯一” 生成情况进行 递推(本题中为钦定填在第一个空位,本题最重要的思想)
    2.阶乘逆元的递推 :inv_jc[i]=inv_jc[i+1]*(i+1)
    3.线性逆元公式 inv[i]=(-MOD/i+MOD)%MOD*inv[MOD%i]%MOD;
*/
posted @ 2025-03-10 14:53  Hanggoash  阅读(15)  评论(0)    收藏  举报
动态线条
动态线条end