• Luogu P5333 [JSOI2019]神经网络


    Link
    一条Hamilton回路可以被拆分成由若干条树上路径组成的环,其中相邻两条树上路径不能同属于一棵树。
    假如我们求出了将一棵树分为若干链的方案数,那么剩下的就是求给环染色,相邻位置颜色不同的方案数。
    第一部分可以用树形背包简单求出,设(f_{u,i,0/1/2})表示(u)的子树内选了(i)条链,(u)的状态为:在一条进入了两个不同的子树的链上/只有一个单点/在一条至少有两个点的单链上,转移分类讨论一下就行了。(一条长度不小于(2)的链应该算两遍!!1)
    现在我们已经求出了(f_k)表示选出(k)条链的带权方案数,直接EGF组合的话会出现环上相邻两条链属于同一棵树的情况。
    考虑容斥,钦定最后环上属于该棵树的链构成(j)个极长连续段,那么(f_k)对该项的贡献为((-1)^{k-j}{k-1choose j-1}f_kk!),所以EGF为

    [widehat f(x)=sumlimits_{i=1}^nf_ii!sumlimits_{j=1}^i(-1)^{i-j}{i-1choose j-1}frac{x^j}{j!} ]

    对于第一棵树而言,限制(1)必须是环的开头,同时首尾不能相同,推完式子可以发现EGF恰好是(frac{widehat f(x)}x)
    最后把所有EGF乘起来就可以得到答案的EGF了。
    注意这个做法在(m=1)的时候有问题的。

    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<algorithm>
    using i64=long long;
    const int N=5007,P=998244353;
    int size[N];i64 s[N][N],f[N][N][3];std::vector<int>e[N];
    int read(){int x;scanf("%d",&x);return x;}
    void inc(i64&a,i64 b){a+=b-P,a+=a>>63&P;}
    void dec(i64&a,i64 b){a-=b,a+=a>>63&P;}
    struct poly{int deg;i64 a[N];poly(int n){deg=n,memset(a,0,8*n+8);}i64&operator[](const int&x){return a[x];}};
    poly operator*(poly f,poly g)
    {
        poly a(f.deg+g.deg);
        for(int i=0;i<=f.deg;++i) for(int j=0;j<=g.deg;++j) inc(a[i+j],f[i]*g[j]%P);
        return a;
    }
    void dfs(int u,int fa)
    {
        static i64 t[N][3];
        size[u]=1,memset(f[u],0,48),f[u][0][1]=1;
        for(int v:e[u])
        {
    	if(v==fa) continue;
    	dfs(v,u),memset(t,0,24*(size[u]+size[v]+1));
    	for(int i=0;i<=size[u];++i)
    	    for(int j=0;j<=size[v];++j)
    	    {
    		for(int k=0;k<3;++k) inc(t[i+j][k],f[u][i][k]*f[v][j][0]%P);
    		inc(t[i+j][2],f[u][i][1]*f[v][j][1]%P),inc(t[i+j+1][0],2*f[u][i][2]*f[v][j][1]%P);
    	    }
    	size[u]+=size[v],memcpy(f[u],t,24*(size[u]+1));
        }
        for(int i=0;i<=size[u];++i) (f[u][i+1][0]+=2*f[u][i][2]+f[u][i][1])%=P,inc(f[u][i][1],f[u][i][2]);
    }
    poly solve()
    {
        int n=read();poly a(n);
        for(int i=1;i<=n;++i) e[i].clear();
        for(int i=1,u,v;i<n;++i) u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
        dfs(1,0);
        for(int i=1;i<=n;++i) a[i]=f[1][i][0];
        for(int i=n;i;--i) for(int j=i+1;j<=n;++j) dec(a[i],a[j]*s[j][i]%P);
        return a;
    }
    int main()
    {
        int m=read();poly ans(0);i64 sum=0,fac=1;ans[0]=s[1][1]=1;
        for(int i=2;i<=5000;++i) for(int j=1;j<=i;++j) s[i][j]=(s[i-1][j-1]+(i-1+j)*s[i-1][j])%P;
        for(int i=1;i<=m;++i) ans=ans*solve();
        for(int i=1;i<=ans.deg;++i) inc(sum,ans[i]*fac%P),fac=fac*i%P;
        printf("%lld",sum);
    }
    
  • 相关阅读:
    最小生成树Prim算法和Kruskal算法(转)
    tarjan有向图的强连通
    匈牙利算法
    字符类数组的常用函数
    三层登录——C#版
    监考风波
    SQL Server 2012 安装——安装 OR 卸载
    SQL Server 2012安装——.net framework 3.5离线安装
    坚定自我 守住寂寞
    浅谈三层
  • 原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/13050988.html
Copyright © 2020-2023  润新知