欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

分组游戏

最编程 2024-08-09 13:31:26
...

题面:

有 n 个人,要分成若干组,对于第 i 个人,它所在的组的人数不能超过 a i ,问分组的方案数模 998244353 的值。

  组与组之间是不可区分的,每组的人也是无序的,但人与人之间是可以区分的。

数据范围
对于 10% 的数据,n ≤ 10。
对于 30% 的数据,n ≤ 50。
对于 50% 的数据,n ≤ 300。
对于 100% 的数据,n ≤ 2000,a i ≤ n。

 显然我一开始是不会的,但经过wty大佬的耐心讲解还是明白了这个神奇的dp。

 由于ai的限制使我们不能常规定义状态 f [ i ] [ j ] 为前 i 个人分成 j 组的方案数。

 所以我们定义 f [ i ] [ j ] 为考虑租的大小为 [ i , n ] ,分完还剩 j 个人的方案数,我们从大到小枚举 i ,f [ 1 ] [ 0 ]  即为答案。

 考虑转移,从 f [ i ] [ j ] 转移到 i - 1  ,设 c n t [ i ] 为 a 大小为i的个数,那么没分到组的就多了 c n t [ i - 1 ] 个人。那么对于这些人,我们枚举 k 代表选 k 个大小为 i - 1 的组,就相当于从 j + c n t [ i - 1 ] 中选取 k * ( i - 1 ) 个,并且将这 k * ( i - 1 ) 个人分成 k 组,预处理 g [ i ] [ j ] 为将 i 个人分成组 j 个的方案数。具体原理见注释。

 代码:

#include<cstdio>
#include<iostream>
using namespace std;
#define maxn 3000
#define mod 998244353
#define int long long
int f[maxn][maxn],g[maxn][maxn],c[maxn][maxn],cnt[maxn];
int n;
main()
{
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    scanf("%lld",&n);
    for(int i=1; i<=n; i++)
    {
        int x;
        scanf("%lld",&x);
        cnt[x]++;
    }
    c[0][0]=1;
    for(int i=1; i<=n; i++)
    {
        c[i][0]=1;
        for(int j=0; j<=i; j++)
            c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
    }
    //g[i][j]->把i个人分成每组j个人方案数
    for(int j=1; j<=n; j++)
    {
        g[0][j]=1;
        for(int i=j; i<=n; i+=j)
            (g[i][j]=g[i-j][j]*c[i-1][j-1])%=mod;
    //为了避免重复,如(1,2)(3,4)和(3,4)(1,2)从g[i-j][j]转移过来时,对于剩下的大小为j的这一组,我们强制将最小的放到最前面,那么对于剩下的我们有i-1个,选取j-1个即可 
    //或者更简单的:g[i][j]=g[i-j][j]*c[i][j],用到g[i][j]的时候,除以 j!(就是不同块的排列数)->如(1,2)(3,4)和(3,4)(1,2),排除重复
    }
    //f[i][j]代表分到大小为[i,n]的组,剩下j个人的方案数,所以f[1][0]为答案
    f[n+1][0]=1;
    for(int i=n+1; i>1; i--)
        for(int j=0; j<=n; j++)
            for(int k=0; k*(i-1)<=j+cnt[i-1]; k++)
                (f[i-1][j+cnt[i-1]-k*(i-1)]+=f[i][j]%mod*c[j+cnt[i-1]][k*(i-1)]%mod*g[k*(i-1)][i-1]%mod)%=mod;
    printf("%lld\n",f[1][0]);
    return 0;
}

原文地址:https://www.cnblogs.com/popo-black-cat/p/11110623.html