[P3244][HNOI2015] 落忆枫音 (树上DAG+组合数)

题意:给一个DAG,然后多加一条边,求在这个图中有多少种不同的生成树;

解法:树上DAG+组合数;

1.树上DAG:因为在DAG上多加了一条边,所以原图会出现环,那么多算的答案就是 rel = ans / ( 环上的点的入度的累乘 ),最后的答案就是 ans-rel(要注意,多加了一条边后,图上可能不止一个环,此时的 rel就是∑tot,tot = ans / ( 环上的点的入度的累乘 ));我们可以在计算这个多算出来的贡献时使用 DP,而在 DP时可以在原图上进行,因为我们的结束边界是多加的那一条边的 end;

2.组合数:当图还是一个DAG时,由乘法原理可得 ans就是所有入度的乘积(这其实是一个定理——朱刘定理);

附上代码:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define ll long long
using namespace std;
const int N = 2e5+10;
const int mod = 1e9+7;

inline int read(){
    int ref=0,x=1;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) ref=ref*10+ch-'0',ch=getchar();
    return ref*x;
}

int n,m,xx,yy;
struct edge{
    int next,to;
}a[N<<4];
int cnt,head[N<<4],x[N],y[N];
int ru[N],vis[N],f[N];
ll ans=1,sum=1;

void add(int u,int v){
    a[++cnt]=(edge){head[u],v};
    head[u]=cnt;
}

ll ksm(ll a,ll b){
    ll rel=1;
    a%=mod;
    while(b){
        if(b&1) rel=(rel*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return rel;
}

ll inv(ll a){ return ksm(a,mod-2); }

void dfs(int x){
    if(vis[x]) return ;
    vis[x]=1;
    if(x==yy){
        f[x]=(sum*inv(ru[x]))%mod;
        return ;
    }
    for(int i=head[x];i;i=a[i].next){
        int v=a[i].to;
        dfs(v);
        f[x]=(f[x]+f[v])%mod;//乘法具有分配律,不影响最终的答案 
    }
    f[x]=(f[x]*inv(ru[x]))%mod;
}

int main()
{
    n=read(),m=read(),xx=read(),yy=read();
    ru[1]++;
    for(int i=1;i<=m;++i){
        int x,y;
        x=read(),y=read();
        add(y,x);
        ru[y]++;
    }
    for(int i=1;i<=n;++i){
        if(i==yy) ans=(ans*(ru[i]+1))%mod;
        else ans=(ans*ru[i])%mod;
        sum=(sum*ru[i])%mod;
    }
    dfs(xx);
    if(xx==yy||yy==1) printf("%lld",sum%mod);
    else printf("%lld",(ans-f[xx]+mod)%mod);
    return 0;
}
原文地址:https://www.cnblogs.com/nnezgy/p/11469152.html