Gym102538H Horrible Cycles
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}\rightarrow f_{i,j}\)。
如果\(i\)是左部点,那么它和\([1,i)\)的所有右部点都有连边。如果\(i\)被选择,那么它会将之前的两条链合并为一条。
即转移为\(f_{i-1,j}+j(j-1)f_{i,j+1}\rightarrow f_{i,j}\)。
还有一个转移,是\(i\)将之前的一条链的两段接起来形成一个环,即将\(f_{i-1,1}\)累加进答案。
我们需要剔除所有的二元环,即将答案减去\(\sum\limits_{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));
}