2021上海市赛-D Zztrans 的班级合照

D. Zztrans 的班级合照

首先要知道在对数排序之后,向两行里面放数的过程中,任何状态下第一行的个数不会小于第二行的个数。

只要出现了下面的状态(第一行的个数少于第二行的个数),则之后怎么填都是违法的。

 所以就考虑是dp.

dp[i][j]的含义是,填到第i个数时,第一行比第二行多j个的情况数。

则将他们排好序之后,

因为有相同的数,这里把他们缩至一个点,,并记录每个数重复num次

(你先这样理解,看到后面就知道了)

第一层循环时i,表示当前枚举到第i个数。

第二层循环是j,即第一行比第二行多j的所有可能情况。

第三层循环数k,表示将a[i]数给第一行k个,给第二行num-k个,那说明如果要转化成第一行比第二行多j个的情况分析如下(如果都是单个数那k的的取值也就只有0和1):

假设转化之后第一行有x+j个数,第二行有x个数,那么转化之前第一行有x+j-k个数,第二行有x-(num-k)个数,则之前的状态为x+j-k-(x-(num-k)) = j+num-2*k

(这里还要保证转化前的j是合法的,也就是l>=0)

因为有相同的数,这里把他们缩至一个点,

假如当前要放的数是3,而3有5个,对于每一种为第一行第二行的成功分配,这5个数都是可以随意交换顺序的,只需要让之前的状态乘上个数的阶乘。

#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;
const int maxn = 5e3+10,mod=998244353 ;
typedef long long ll;
ll dp[maxn][maxn],f[maxn];
int a[maxn],vis[maxn],n;

void init(){

    f[1]=1;
    for(int i = 2; i <=5e3 ;i++){
        f[i]=(f[i-1]*i)%mod;
    }
}
int main()
{
    init();
    scanf("%d",&n);
    for(int i = 1; i <= n ; i++){
        scanf("%d",&a[i]);
        vis[a[i]]++;
    }
    sort(a+1,a+n+1);
    dp[0][0]=1;
    int tot=unique(a+1,a+n+1)-a-1;
    for(int i = 1; i <= tot; i++){
        int num=vis[a[i]];
        for(int j = 0; j <= n ; j++){
            for(int k = 0; k <= num; k++){
                int l=j+num-2*k;/之前的状态
                if(l<0) break;
                dp[i][j]=(dp[i][j]+dp[i-1][l]*f[num]%mod)%mod;
            }
        }
    }printf("%lld\n",dp[tot][0]);
    return 0;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值