2019.01.19-2018年6月NEYC集训counting

题目描述:

羽月最近发现,她发动能力的过程是这样的:
构建一个 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<bits/stdc++.h>
#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)continue;
            for(int k=0;k<=n;k++){
                if(k+j-1>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;
}
View Code
原文地址:https://www.cnblogs.com/Jessie-/p/10291538.html