题目描述:
羽月最近发现,她发动能力的过程是这样的:
构建一个 V 个点的有向图 G,初始为没有任何边,接下来羽月在脑中构建出一个长度为 E 的边的序列,序列中元素两两不同,然后羽月将这些边依次加入图中,每次加入之后计算当前图的强连通分量个数并记下来,最后得到一个长度为E 的序列,这个序列就是能力的效果。注意到,可能存在边的序列不同而能力效果相同的情况,所以羽月想请你帮她计算能发动的不同能力个数,答案对 998244353 取模。你需要对于1<=E<=V*(V-1)的所有 E 计算答案。算法标签:前缀和优化dp
思路:
考虑对于一个可行的序列,令组成方案表示成一个大的包含多个点的强连通分量,其余点都为单独的连通分量能表示所以情况。
我们考虑一次把x个点缩进大的连通分量,需要x+1条边。
对于已有j个点在大连通分量内时,边至多只有j*j+(n-j)*(n-j+1)/2+j*(n-j)条。
倘若令k为缩过几次点,则边至少有j+k-1条。
考虑dp
令f[i][j][k]表示加到第i条边,有j个点在大连通分量内,锁了k次点。
式子是:
f[i][j][k]=f[i-1][j][k]+∑f[i-1][j-p][k]
于是考虑前缀和优化,记sum[j-1][k]为∑f[i-1][j-p][k]
以下代码:
#include#define il inline#define _(d) while(d(isdigit(ch=getchar())))using namespace std;const int N=105,p=998244353;int n,f[2][N][N],op,sum[N][N];il int read(){ int x,f=1;char ch; _(!)ch=='-'?f=-1:f;x=ch^48; _()x=(x<<1)+(x<<3)+(ch^48); return f*x;}il int mu(int x,int y){ if(x+y>=p)return x+y-p; return x+y;}int main(){ n=read();f[0][1][0]=1;for(int i=0;i<=n;i++)sum[1][0]=1; for(int i=1;i<=n*(n-1);i++){ op^=1; for(int j=1;j<=n;j++)for(int k=0;k<=n;k++)f[op][j][k]=0; for(int j=1;j<=n;j++){ if(j*(j-1)+(n-j)*(n-j-1)/2+j*(n-j) i)break; if(k)f[op][j][k]=mu(f[op^1][j][k],sum[j-1][k-1]); else f[op][j][k]=f[op^1][j][k]; } } int ans=0; for(int j=1;j<=n;j++){ for(int k=0;k<=n;k++){ sum[j][k]=mu(sum[j-1][k],f[op][j][k]); ans=mu(ans,f[op][j][k]); } } printf("%d ",ans); }puts(""); return 0;}