首先要知道在对数排序之后,向两行里面放数的过程中,任何状态下第一行的个数不会小于第二行的个数。
只要出现了下面的状态(第一行的个数少于第二行的个数),则之后怎么填都是违法的。
所以就考虑是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;
}