Link
将(2n)个点进行排序,使得每个左部点与前面的所有右部点都有连边,然后将其编号为([1,2n])。
对于一个环而言,假如我们只保留环中([1,x])范围内的点,那么这个环会被拆成若干条链。
我们以此为状态进行dp,设(f_{i,j})表示只保留([1,i])范围内的点,形成(j)条链的方案数。
显然初始状态为(f_{0,0}=1),因此考虑加入点(i)会造成什么影响。
如果(i)是右部点,那么它和([1,i))的所有点都没有连边。如果(i)被选择,那么它会单独成一条链。
即转移为(f_{i-1,j}+f_{i-1,j-1}
ightarrow f_{i,j})。
如果(i)是左部点,那么它和([1,i))的所有右部点都有连边。如果(i)被选择,那么它会将之前的两条链合并为一条。
即转移为(f_{i-1,j}+j(j-1)f_{i,j+1}
ightarrow f_{i,j})。
还有一个转移,是(i)将之前的一条链的两段接起来形成一个环,即将(f_{i-1,1})累加进答案。
我们需要剔除所有的二元环,即将答案减去(sumlimits_{i=1}^na_i)。
因为链是有序的,所以会将一个环计算两遍,因此最后还要将答案除以(2)。
#include<cstdio>
#include<algorithm>
const int N=5007,P=998244353,i2=P-P/2;
int read(){int x;scanf("%d",&x);return x;}
void inc(int&a,int b){a+=b-P,a+=a>>31&P;}
void dec(int&a,int b){a-=b,a+=a>>31&P;}
int mul(int a,int b){return 1ll*a*b%P;}
int a[N],f[N];
int main()
{
int n=read(),ans=0;
for(int i=1;i<=n;++i) dec(ans,a[i]=read());
std::sort(a+1,a+n+1),f[0]=1;
for(int i=1;i<=n;++i)
{
for(int j=a[i-1]+1;j<=a[i];++j) for(int k=j;k;--k) inc(f[k],f[k-1]);
inc(ans,f[1]);
for(int j=1;j<=a[i];++j) inc(f[j-1],mul(j*(j-1),f[j]));
}
printf("%d",mul(ans,P-P/2));
}