有标号的DAG计数I~IV

有标号的DAG计数

最近心血来潮来写一写这个玩意儿。

请特别注意定义生成函数时下标的起始位置。

有标号的DAG计数I

求\(n\)点带标号\(DAG\)的数量模\(10007\)。\(n\le5000\)。

数据范围显然\(O(n^2)\)。设\(f_i\)表示答案,枚举\(DAG\)中入度为零的点的数量\(j\),方案数为\(\binom ij\),将图拆成两个部分,前\(j\)个点向后\(i-j\)个点的连边任意,方案数为\(2^{j(i-j)}\),后\(j\)个点构成一张\(DAG\),方案数为\(f_{i-j}\)。

但是这样并不保证入度为零的点只有\(j\)个,所以需要容斥。

\[f_i=\sum_{j=1}^i(-1)^{j-1}\binom ij2^{j(i-j)}f_{i-j}\]

有标号的DAG计数II

求\(n\)点带标号\(DAG\)的数量模\(998244353\)。\(n\le100000\)。

首先\(2^{j(i-j)}\)需要处理一下。需要想办法把\(ij\)项去掉。

\(j(i-j)=\frac{i^2}{2}-\frac{j^2}{2}-\frac{(i-j)^2}{2}\)。

好在模\(998244353\)意义下存在\(2\)的二次剩余。所以可以把原递推式划一划。

\[f_i=\sum_{j=1}^i(-1)^{j-1}\frac{i!}{j!(i-j)!}\frac{\sqrt2^{i^2}}{\sqrt2^{j^2}\sqrt2^{(i-j)^2}}f_{i-j}\]

令\(F(x)=\sum_{i=0}^n\frac{f_i}{i!\sqrt2^{i^2}}x^i,G(x)=\sum_{i=1}^n\frac{(-1)^{i-1}}{i!\sqrt2^{i^2}}x^i\),

则有\(F(x)=G(x)F(x)+1\),故\(F(x)=\frac{1}{1-G(x)}\)。

多项式求逆即可。

有标号的DAG计数III

求\(n\)点带标号\(DAG\)的数量模\(10007\),要求图弱连通。\(n\le5000\)。

首先还是把前面的\(f_i\)求出来,然后枚举某一个点所在的弱连通\(DAG\)大小,有递推式:

\[g_i=f_i-\sum_{j=1}^{i-1}\binom{i-1}{j}f_jg_{i-j}\]

有标号的DAG计数IV

求\(n\)点带标号\(DAG\)的数量模\(998244353\),要求图弱连通。\(n\le100000\)。

划一下式子:

\[\frac{g_i}{(i-1)!}=\frac{f_i}{(i-1)!}-\sum_{j=1}^{i-1}\frac{f_j}{j!}\frac{g_{i-j}}{(i-j-1)!}\]

设\(F(x)=\sum_{i=1}^n\frac{f_i}{i!}x^i,G(x)=\sum_{i=1}^n\frac{g_i}{(i-1)!}x^i,H(x)=\sum_{i=1}^n\frac{f_i}{(i-1)!}x^i\),

则有\(G(x)=H(x)-F(x)G(x)\),故\(G(x)=\frac{H(x)}{1+F(x)}\)。

以上是方法一。

方法二非常地有趣。我们发现一个带标号的\(DAG\)不要求弱连通实质上是由若干个弱连通\(DAG\)构成的,我们设第二题答案的指数生成函数为\(F(x)\),第四题答案的指数生成函数为\(G(x)\),于是就有\[F(x)=e^{G(x)}\]
也就是\[G(x)=\ln F(x)\]

多项式求\(\ln\)即可。

code

有标号的DAG计数I

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 5005;
const int mod = 10007;
int n,C[N][N],bin[N*N],f[N];
int main(){
    scanf("%d",&n);
    for (int i=C[0][0]=1;i<=n;++i)
        for (int j=C[i][0]=1;j<=i;++j)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    for (int i=bin[0]=1;i<=n*n;++i) bin[i]=(bin[i-1]<<1)%mod;
    f[0]=1;
    for (int i=1;i<=n;++i)
        for (int j=1;j<=i;++j)
            f[i]=(f[i]+1ll*(j&1?1:mod-1)*bin[j*(i-j)]%mod*C[i][j]%mod*f[i-j])%mod;
    printf("%d\n",f[n]);return 0;
}

有标号的DAG计数II

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 3e5+5;
const int mod = 998244353;
const int rem = 882049182;
int n,jc[N],jcn[N],g[N],f[N],rev[N],og[N];
int fastpow(int a,int b){
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void init(int len){
    int l=0;while ((1<<l)<len) ++l;
    for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1);
}
void ntt(int *P,int op,int len){
    for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
    for (int i=1;i<len;i<<=1){
        int W=fastpow(3,(mod-1)/(i<<1));
        if (op==-1) W=fastpow(W,mod-2);
        og[0]=1;for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
        for (int p=i<<1,j=0;j<len;j+=p)
            for (int k=0;k<i;++k){
                int x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
                P[j+k]=(x+y)%mod;P[j+k+i]=(x-y+mod)%mod;
            }
    }
    if (op==-1) for (int i=0,Inv=fastpow(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}
int tmp[N];
void Get_Inv(int *a,int *b,int len){
    if (len==1) {b[0]=fastpow(a[0],mod-2)%mod;return;}
    Get_Inv(a,b,len>>1);
    for (int i=0;i<len;++i) tmp[i]=a[i];
    init(len<<1);ntt(tmp,1,len<<1);ntt(b,1,len<<1);
    for (int i=0;i<len<<1;++i) tmp[i]=(2ll*b[i]-1ll*b[i]*b[i]%mod*tmp[i]%mod+mod)%mod;
    ntt(tmp,-1,len<<1);
    for (int i=0;i<len;++i) b[i]=tmp[i],b[i+len]=0;
}
int main(){
    scanf("%d",&n);g[0]=jc[0]=jcn[0]=1;
    for (int i=1,inv=fastpow(rem,mod-2);i<=n;++i){
        jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=fastpow(jc[i],mod-2);
        g[i]=1ll*(i&1?mod-1:1)*fastpow(inv,1ll*i*i%(mod-1))%mod*jcn[i]%mod;
    }
    int len=1;while (len<=n) len<<=1;
    Get_Inv(g,f,len);
    printf("%lld\n",1ll*f[n]*fastpow(rem,1ll*n*n%(mod-1))%mod*jc[n]%mod);
    return 0;
}

有标号的DAG计数III

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 5005;
const int mod = 10007;
int n,bin[N*N],C[N][N],f[N],g[N];
int main(){
    scanf("%d",&n);
    for (int i=C[0][0]=1;i<=n;++i)
        for (int j=C[i][0]=1;j<=i;++j)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    for (int i=bin[0]=1;i<=n*n;++i) bin[i]=(bin[i-1]<<1)%mod;
    f[0]=g[0]=1;
    for (int i=1;i<=n;++i){
        for (int j=1;j<=i;++j)
            f[i]=(f[i]+1ll*(j&1?1:mod-1)*C[i][j]*bin[j*(i-j)]*f[i-j])%mod;
        g[i]=f[i];
        for (int j=1;j<i;++j)
            g[i]=(g[i]-1ll*C[i-1][j]*f[j]*g[i-j]%mod+mod)%mod;
    }
    printf("%d\n",g[n]);return 0;
}

有标号的DAG计数IV

方法一:多项式求逆。

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 3e5+5;
const int mod = 998244353;
const int rem = 882049182;
int n,jc[N],jcn[N],g[N],f[N],h[N],rev[N],og[N];
int fastpow(int a,int b){
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void init(int len){
    int l=0;while ((1<<l)<len) ++l;
    for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1);
}
void ntt(int *P,int op,int len){
    for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
    for (int i=1;i<len;i<<=1){
        int W=fastpow(3,(mod-1)/(i<<1));
        if (op==-1) W=fastpow(W,mod-2);
        og[0]=1;for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
        for (int p=i<<1,j=0;j<len;j+=p)
            for (int k=0;k<i;++k){
                int x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
                P[j+k]=(x+y)%mod;P[j+k+i]=(x-y+mod)%mod;
            }
    }
    if (op==-1) for (int i=0,Inv=fastpow(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}
int tmp[N];
void Get_Inv(int *a,int *b,int len){
    if (len==1) {b[0]=fastpow(a[0],mod-2)%mod;return;}
    Get_Inv(a,b,len>>1);
    for (int i=0;i<len;++i) tmp[i]=a[i];
    init(len<<1);ntt(tmp,1,len<<1);ntt(b,1,len<<1);
    for (int i=0;i<len<<1;++i) tmp[i]=(2ll*b[i]-1ll*b[i]*b[i]%mod*tmp[i]%mod+mod)%mod;
    ntt(tmp,-1,len<<1);
    for (int i=0;i<len;++i) b[i]=tmp[i],b[i+len]=tmp[i]=tmp[i+len]=0;
}
int main(){
    scanf("%d",&n);g[0]=jc[0]=jcn[0]=1;
    for (int i=1,inv=fastpow(rem,mod-2);i<=n;++i){
        jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=fastpow(jc[i],mod-2);
        g[i]=1ll*(i&1?mod-1:1)*fastpow(inv,1ll*i*i%(mod-1))%mod*jcn[i]%mod;
    }
    int len=1;while (len<=n) len<<=1;
    Get_Inv(g,f,len);
    for (int i=0;i<=n;++i){
        f[i]=1ll*f[i]*fastpow(rem,1ll*i*i%(mod-1))%mod;
        h[i]=1ll*f[i]*i%mod;g[i]=0;
    }
    for (int i=n+1;i<len;++i) f[i]=g[i]=0;
    Get_Inv(f,g,len);
    for (int i=n+1;i<len;++i) g[i]=0;
    init(len);ntt(g,1,len);ntt(h,1,len);
    for (int i=0;i<len;++i) g[i]=1ll*g[i]*h[i]%mod;
    ntt(g,-1,len);
    printf("%lld\n",1ll*g[n]*jc[n-1]%mod);return 0;
}

方法二:多项式求\(\ln\)。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 3e5+5;
const int mod = 998244353;
const int rem = 882049182;
int n,inv[N],jc[N],jcn[N],g[N],f[N],rev[N],og[N];
int fastpow(int a,int b){
    int res=1;
    while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
    return res;
}
void init(int len){
    int l=0;while ((1<<l)<len) ++l;
    for (int i=0;i<len;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l-1);
}
void ntt(int *P,int op,int len){
    for (int i=0;i<len;++i) if (i<rev[i]) swap(P[i],P[rev[i]]);
    for (int i=1;i<len;i<<=1){
        int W=fastpow(3,(mod-1)/(i<<1));
        if (op==-1) W=fastpow(W,mod-2);
        og[0]=1;for (int j=1;j<i;++j) og[j]=1ll*og[j-1]*W%mod;
        for (int p=i<<1,j=0;j<len;j+=p)
            for (int k=0;k<i;++k){
                int x=P[j+k],y=1ll*og[k]*P[j+k+i]%mod;
                P[j+k]=(x+y)%mod;P[j+k+i]=(x-y+mod)%mod;
            }
    }
    if (op==-1) for (int i=0,Inv=fastpow(len,mod-2);i<len;++i) P[i]=1ll*P[i]*Inv%mod;
}
int s1[N];
void Get_Inv(int *a,int *b,int len){
    if (len==1) {b[0]=fastpow(a[0],mod-2)%mod;return;}
    Get_Inv(a,b,len>>1);
    for (int i=0;i<len;++i) s1[i]=a[i];
    init(len<<1);ntt(s1,1,len<<1);ntt(b,1,len<<1);
    for (int i=0;i<len<<1;++i) s1[i]=(2ll*b[i]-1ll*b[i]*b[i]%mod*s1[i]%mod+mod)%mod;
    ntt(s1,-1,len<<1);
    for (int i=0;i<len;++i) b[i]=s1[i],b[i+len]=s1[i]=s1[i+len]=0;
}
void Get_Der(int *a,int *b,int len){
    for (int i=1;i<len;++i) b[i-1]=1ll*i*a[i]%mod;
    b[len-1]=0;
}
void Get_Int(int *a,int *b,int len){
    for (int i=1;i<len;++i) b[i]=1ll*inv[i]*a[i-1]%mod;
    b[0]=0;
}
int s2[N],s3[N];
void Get_ln(int *a,int *b,int len){
    Get_Der(a,s2,len);Get_Inv(a,s3,len);
    init(len<<1);ntt(s2,1,len<<1);ntt(s3,1,len<<1);
    for (int i=0;i<len<<1;++i) s2[i]=1ll*s2[i]*s3[i]%mod;
    ntt(s2,-1,len<<1);Get_Int(s2,b,len);
    for (int i=0;i<len<<1;++i) s2[i]=s3[i]=0;
}
int main(){
    scanf("%d",&n);g[0]=jc[0]=jcn[0]=1;
    for (int i=1,Inv=fastpow(rem,mod-2);i<=n;++i){
        inv[i]=i==1?1:1ll*inv[mod%i]*(mod-mod/i)%mod;
        jc[i]=1ll*jc[i-1]*i%mod,jcn[i]=fastpow(jc[i],mod-2);
        g[i]=1ll*(i&1?mod-1:1)*fastpow(Inv,1ll*i*i%(mod-1))%mod*jcn[i]%mod;
    }
    int len=1;while (len<=n) len<<=1;
    Get_Inv(g,f,len);
    for (int i=0;i<=n;++i) f[i]=1ll*f[i]*fastpow(rem,1ll*i*i%(mod-1))%mod;
    for (int i=n+1;i<len;++i) f[i]=0;
    memset(g,0,sizeof(g));Get_ln(f,g,len);
    printf("%lld\n",1ll*g[n]*jc[n]%mod);
    return 0;
}

原文地址:https://www.cnblogs.com/zhoushuyu/p/10077241.html

时间: 2024-10-02 00:11:13

有标号的DAG计数I~IV的相关文章

有标号的DAG计数 III

Description 给定一正整数n,对n个点有标号的有向无环图进行计数,这里加一个限制:此图必须是弱连通图.输出答案 mod 10007 的结果. Solution 弱连通图即把边变成无向之后成为连通的图 考虑补集转换,用 \(DAG\) 的方案数减去不连通的方案数 设 \(f[i]\) 为大小为 \(i\) 的\(DAG\)的方案数 可以像 \(DAG I\) 那样求出来 \(g[i]\) 为弱连通图的方案数 \(g[n]=f[n]-\sum_{i=1}^{n}g[i]*f[i-j]*C_

[COGS 2353 &amp; 2356] 有标号的DAG计数 容斥原理

COGS 2353 题意 问 n 个点的带标号 DAG 有多少个. n <= 5000 . 分析 DAG 的突破口在于度数为 0 的点, 我们每次将其删去, 则还有一些度数为 0 的点. 设 $f_n$ 为 n 个点的带标号 DAG 个数, 奠基 $f_0 = 1$ , 答案为 $f_n$ . 我们考虑容斥原理, 用至少有 1 个度数为 0 的点的 DAG 个数, 减去至少有 2 个度数为 0 的点的 DAG 个数, 加上至少有 3 个度数为 0 的点的 DAG 个数, ... $$f_n =

有标号的DAG计数系列问题

传送门 II 设 \(f_i\) 表示 \(i\) 个点的答案 那么枚举至少 \(j\) 个点的出度为 \(0\) \[\sum_{j=0}^{i}(-1)^j\binom{i}{j}f_{i-j}2^{(i-j)j}=0\] 所以 \[f_i=\sum_{j=1}^{i}(-1)^{j+1}\binom{i}{j}f_{i-j}2^{(i-j)j}\] 即 \[\frac{f_i}{i!}=\sum_{j=0}^{i-1}\frac{f_j}{j!}\frac{(-1)^{i-j+1}}{(i

有标号的DAG/强连通图计数

COGS索引 一堆神仙容斥+多项式-- 有标号的DAG计数 I 考虑\(O(n^2)\)做法:设\(f_i\)表示总共有\(i\)个点的DAG数量,转移考虑枚举DAG上所有出度为\(0\)的点,剩下的点可以选择连向它,剩下的点之间也可以连边. 但是注意到这样子转移可能会存在剩下的点中有点没有出度的情况,考虑容斥解决:设枚举的出度为\(0\)的点的个数为\(i\)时的容斥系数为\(f_i\),那么一个实际上存在\(x\)个出度为\(0\)的点的DAG的贡献就是\(\sum\limits_{i=1}

有标号的DAG图计数1~4

前言 我什么都不会,菜的被关了起来. 有标号的DAG图I Solution 考虑递推,设\(f_i\)表示i个点的答案,显然这个东西是可以组合数+容斥递推? 设\(f_i\)表示i个点的答案,我们考虑假设现在有j个点入度为1,那么可以选出的点就是一个组合数\(C_i^j\),边的可能性有两种,对应的就是\(2^{j*(i-j)}\),然后接着搞,肯定这样子算会有重复的,所以容斥一下然后和以前的答案乘起来就好了. \(f_i=\sum_{j=1}^{i}f_{i-j}*-1^{j-1}*C_i^j

无标号树的计数原理(组合计数,背包问题,隔板法,树的重心)

闲话 一个计数问题入门级选手来搞这种东西 最初的动力来自高一化学课有机物(滑稽).<同步导练>出了个这样的选择题. 一个结构极其庞大的烷烃(二十几个碳原子),求它的主链长度. 这不是个求树的直径的裸题么?!OI选手扫两眼就出来了,然而别的同学费劲心思找完了还是错的. 于是第一次在常规课中体验到作为OIer的优越感...... 又是一节课,芙蓉姐开始要我们画己烷.庚烷的同分异构体?! 这不是等于要求节点数为\(n\),点度数不超过\(4\)的无标号的无根树个数吗?没见过,但还是有一点DP思想,蒟

cojs 强连通图计数1-2 题解报告

OwO 题目含义都是一样的,只是数据范围扩大了 对于n<=7的问题,我们直接暴力搜索就可以了 对于n<=1000的问题,我们不难联想到<主旋律>这一道题 没错,只需要把方程改一改就可以了 首先我们考虑不合法的方案强连通分量缩点后一定是DAG 考虑子问题:DAG计数 做法可以参考<cojs DAG计数1-4 题解报告> 这里给出转移方程 f(n)=sigma((-1)^(k-1)*C(n,k)*2^(k*(n-k))*f(n-k)) 如果考虑上强连通分量缩点的情况呢? 我

图的计数

无标号计数与带标号计数 无标号就是在观测上不考虑个体的差异, 带标号就是在观测上考虑个体的差异. 对于无标号计数, 我们可以按照某种关键值, 给所有的元素进行定序, 所有无标号计数等价于定序计数. 对于带标号计数, 我们可以任意设置关键值, 所以带标号计数等价于不定序计数. 最常见的例子就是组合与排列, 它们还满足一组特殊的关系: 组合数 * n! = 排列数. 有向图 / 无向图 带标号无向图的 度数的K次方 的和 分析 $ans = n \sum_{d = 0} ^ {n - 1} \bin

51Nod 1806 wangyurzee的树

1806 wangyurzee的树 链接:http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1806 想法:因为$m \le 17$,所以用容斥统计一下.即限定一些$u_i$的度数为$d_i$,然后变成Prufer统计带标号树的计数. #include< cstdio > #define FILE(F) freopen(F".in","r",stdin),freopen(F&quo