LINK

题意

n n n个数字(彼此不同,就算大小相等也不同)

分成大小为 n 2 \frac{n}{2} 2n的两个不递减序列,且第一个序列的每个位置都不小于第二个序列的对应位置

求方案数


我们先把数组 a a a排个序,不排序怎么做

T 1 T1 T1

如果没有两个序列对应位置的大小关系限制,那么很好做

只需要考虑 a i a_i ai是放在第一个序列还是放在第二个序列即可

这样我们定义 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个数,第二个序列放了 j j j个第一个序列放了 i − j i-j ij个的方案

但是又考虑到数字相同时,放的顺序可以任意

所以我们直接把相同的数字 x x x缩成一个点,设共有 t t t x x x

枚举分配给第一个序列 q q q x x x,分配给第二个序列 t − q t-q tq x x x

这样 d p dp dp就没有考虑顺序,所以最后乘以一个 t ! t! t!即可

T 2 T2 T2

现在要求第一个序列的对应位置不小于第二个序列,也就是任意时刻 j > = i − j j>=i-j j>=ij

这个限制比较巧妙,利用后面加进来的数字更大的特性完美满足了这个限制

d p dp dp数组的含义不变,但是所有 j < i − j j<i-j j<ij的状态都变成不合法状态

其实仔细观察转移方程,可以用数据结构优化区间和做到 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
int f[5009][5009],n,ans,fac[5009],a[5009],pos[5009];
void upd(int &x,int y){ x = ( x+y )%mod; }
vector<int>vec;
signed main()
{
	fac[0] = 1;
	for(int i=1;i<=5000;i++)	fac[i] = fac[i-1]*i%mod;
	cin >> n;
	for(int i=1;i<=n;i++)	cin >> a[i], pos[a[i]]++;
	sort( a+1,a+1+n );
	a[0] = a[1];  a[n+1] = -1;
	int temp = 0;
	for(int i=1;i<=n+1;i++)
	{
		if( a[i]==a[i-1] )	temp++;
		else
		{
			vec.push_back( temp );
			temp = 1;
		}
	}
	f[0][0] = 1;
	int pre = 0;
	for(auto v:vec )
	{
		pre += v;
		for(int j=min(pre,n/2);j>=pre-j;j--)
		for(int q=0;q<=v && q<=j ;q++)
			upd( f[pre][j],f[pre-v][j-q] );
	}
	int ans = f[n][n/2];
	for(int i=1;i<=n;i++)	ans = ans*fac[pos[i]]%mod;
	cout << ans;
}