带标号的DAG计数(N^2做法),无向图三元环四元环计数和带标号二分图计数(O(N)做法)

一、带标号DAG计数:

 前置芝士:

   DAG:有向无环图

  带标号:有序的DAG(1->2->3和1->3->2计算成两种不同的DAG)

  容斥原理:

  

带标号DAG计数 I:

    给定N个点,求带标号DAG数量,不强制联通。N <= 5000

  假设 f[i] 代表 i 个点的答案

  考虑选 j 个点,使他们的入度为0,连向剩余的点,剩余的点任意DAG均可。

  这样做是一定不会出现环的,因为新建的点(j个入度为0的点)不会有边连向他们。

  但是有一个问题,不能保证剩余的点中没有入度为0的点,于是上容斥原理。

  有递推式:

        

    四项分别代表: 容斥系数,在i个中选j个,j个点连向i-j个点连边方案数,i-j个点任意DAG个数

  f[N]即为答案

p2p[0]=1,C[0][0]=1;
    for(int i=1;i<=N;i++){
        C[i][0]=1;
        for(int j=1;j<=i;j++){
            C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
        }
    }
    for(int i=1;i<=N*N;i++) p2p[i]=(p2p[i-1]<<1)%mod;
    f[0]=1;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=i;j++){
            ll opt=(j&1)?1:-1;
            f[i]=(f[i]+opt*p2p[j*(i-j)]*C[i][j]%mod*f[i-j]%mod))%mod;
        }
    }
    cout<<((ll)f[N]+mod)%mod<<endl;

  

  

 带标号DAG计数 II:

    给定N个点,求带标号DAG数量,要求弱联通。N <= 5000

  假设g[i]代表i个点弱联通的答案。

  那么只需要用f[i]减去不联通的DAG数量即可。

  我们可以考虑任意选一个点T,枚举包含T的联通DAG大小,其余的点不与此DAG联通。

  有递推式(这里的j表示的是不与T联通的点数):

        

  这三项分别表示:在除T外的i-1个点中选j个,j个点任意DAG,i-j个点任意联通DAG个数

  这样做是不会算重也不会少算的,因为任意一个不连通的DAG一定包含两部分:和T联通的DAG和剩余的。

  当这两个其中至少一个不同DAG就会不同。

  g[N]即为答案

  

    C[0][0]=1,p2p[0]=1,f[0]=1;
    for(int i=1;i<=N;i++){
        C[i][0]=1;
        for(int j=1;j<=i;j++){
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
        }
    }
    for(int i=1;i<=N*N;i++) p2p[i]=(p2p[i-1]<<1)%mod;
    for(int i=1;i<=N;i++){
        for(int j=1;j<=i;j++){
            ll opt=(j&1)?1:-1;
            f[i]=(f[i]+opt*C[i][j]*p2p[j*(i-j)]%mod*f[i-j]%mod)%mod;
        }
    }
    for(int i=1;i<=N;i++){
        g[i]=f[i];
        for(int j=1;j<i;j++){
            g[i]=(g[i]-(ll)C[i-1][j]*f[j]%mod*g[i-j]%mod)%mod;
        }
    }
    cout<<(g[N]+mod)%mod<<endl;

 在以上的基础上使用多项式求逆可以达到nlogn的复杂度,这里先不提。

二、无向图三元环四元环计数:

  给定一个无向图,求三元环数量。 N <= 100000  M <= 200000

无向图三元环计数(根号分治): 

  先制定一个点之间比较的规则,然后将无向图改为有向图,将无向边改成由小的点指向大的点的有向边。

  这样做是显然不会出现环的。

  那么新图的三元环(i,j,k)一定就是这样:

  

  那我们就枚举每一个点u,先把所有能直接到的点v打上标记,再枚举这些点v出边到达的点看是否有点u的标记即可

  很明显枚举(u,v)时每条边只会被枚举一次,时间复杂度就是(d[i]代表i的出度):

      

   考虑改变比较规则来优化时间:

  •  当两个点的度数不同时,比较两个点的度数。
  •  当两个点的度数相同时,比较两个点的编号。

   当一个点在原图出度小于M时,显然新图出度不会变大,时间M

   当一个点在原图出度大于M时,由于比较的规则,这个点在新图只会连向出度也大于M的点因为出度大于M的点最多只有M个,所以时间最多M

   综上,时间复杂度为O(M+MM)。

  

struct chr{
    int a,b;
}e[maxn];
bool cmp(int a,int b){
    return d[a]==d[b]?a<b:d[a]<d[b];
}
int MAIN(){ 
    cin>>N>>M;
    for(int i=1;i<=M;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        chr now=(chr){a,b};
        d[a]++,d[b]++;
        e[i]=now;
    }
    for(int i=1;i<=M;i++){
        if(cmp(e[i].a,e[i].b)) add(e[i].a,e[i].b);
        else add(e[i].b,e[i].a);
    }
    int ans=0;
    for(int i=1;i<=N;i++){
        for(auto j:ed[i]) vis[j]=i;
        for(auto j:ed[i]){
            for(auto k:ed[j]){
                ans+=vis[k]==i;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

无向图三元环计数(bitset):

  这种做法就是用bitset优化暴力。

  给每一个点开一个bitset,代表这个点能直接到达的点。

  直接枚举每条边,将两个端点按位与,1的个数就是这条边所在三元环的个数,加到ans上即可。

  由于每个环都被加了3次,所以最后ans要除以3。

  时间O(NMw)  空间O(N2)

  有些题比较卡空间,可以类似分块,设置一个阈值B。

  度数大于B的就预处理出完整的bitset,小于的可以在计算时算出。

  大于B的点不会超过MB个。

  时间O(NMw+BM)  空间O(NMB)

  

bitset<maxn>b[640],p,q;
int MAIN(){ 
    cin>>N>>M;
    for(int i=1;i<=M;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        d[a]++,d[b]++;
        add(a,b),add(b,a);
    }
    int sqt=sqrt(M<<1);
    int cnt=0;
    for(int i=1;i<=N;i++){
        if(d[i]>sqt){
            tag[i]=++cnt;
            for(auto c:ed[i]) b[cnt][c]=1;
        }
    }
    int ans=0;
    for(int i=1;i<=N;i++){
        bitset<maxn> *A,*B;
        if(tag[i]) A=&b[tag[i]];
        else{
            p.reset();
            for(auto l:ed[i]) p[l]=1;
            A=&p;
        }
        for(auto j:ed[i]){
            if(tag[j]) B=&b[tag[j]];
            else{
                q.reset();
                for(auto l:ed[j]) q[l]=1;
                B=&q;
            }
            ans+=(*A&*B).count();
        }
    }
    cout<<ans/6<<endl;//这里除以6是因为我每个边都算了两遍
    return 0;
}

无向图四元环计数:  

  给定一个无向图,求四元环数量。 N <= 100000  M <= 200000

  类比三元环的根号分治,将无向图改为有向无环图。

  四元环在新图中有这几种表现形式:

  

  容易发现一定会有一个这样的结构:

  

   余下的两条边方向任意。

  于是我们枚举一个点u,作为这个结构对面的点,之后枚举原图所有能到达的点v,再枚举v在新图能到达的点w,每次将ans加上cnt[w],再让cnt[w]++。

  相当于两半拼成一个完整的环。

  但是有一个问题,就是上面的第三种情况会算重,所以仅当w>u时才可以进行。

  时间复杂度同三元环 O(M+MM)。

bool cmp(int a,int b){
    return d[a]==d[b]?a<b:d[a]<d[b];
}
int MAIN(){
    cin>>N>>M;
    for(int i=1;i<=M;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        d[a]++,d[b]++;
        add(a,b),add(b,a);
    }
    for(int i=1;i<=N;i++){
        for(auto j:ed[i]){
            if(cmp(i,j)) addg(i,j);
        }
    }
    int ans=0;
    for(int i=1;i<=N;i++){
        for(auto j:ed[i]){
            for(auto k:edg[j]){
                if(cmp(i,k)) ans+=cnt[k]++;
            }
        }
        for(auto j:ed[i]){
            for(auto k:edg[j]){
                if(cmp(i,k)) cnt[k]=0;
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

三、带标号二分图计数:

  给定点数N,求带标号二分图个数。 N <= 1000000

  这里的二分图染成了两种颜色,边不同或者点的颜色不同都算不同。

  枚举黑色点的个数,再任意将黑白点之间连边即可。

  于是就有:

  

int qp(int x,ll y){
    int ans=1;
    while(y){
        if(y&1) ans=((ll)ans*x)%mod;
        y>>=1;
        x=((ll)x*x)%mod;
    }
    return ans;
}
int C(int a,int b){
    return (ll)jc[a]*jcny[b]%mod*jcny[a-b]%mod;
}
int MAIN(){
    cin>>N;
    jc[0]=1,jcny[0]=1;
    for(int i=1;i<=N;i++) jc[i]=jc[i-1]*(ll)i%mod;
    jcny[N]=qp(jc[N],mod-2);
    for(int i=N-1;i;--i) jcny[i]=(ll)jcny[i+1]*(i+1)%mod;
    int ans=0;
    for(int i=0;i<=N;i++){
        ans=(ans+(ll)C(N,i)*qp(2,(ll)i*(N-i))%mod)%mod;
    }
    cout<<ans<<endl;
    return 0;
}

  

posted @   xxqz  阅读(550)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示