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} 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));
}
原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/12668963.html