[HNOI2015][bzoj4011] 落叶枫音 [拓扑DP]

题面

传送门

思路

首先有一个结论(应该是有比较大的利用价值的):

有向无环图的生成外向树树个数等于所有入度非0的点的入度乘积

然后这道题里面,唯一不合拍的因素就是这里有一条可能成环的边

我们可以把这条边先加入原来的DAG里面,然后用上面的结论算出总生成树个数,再减去不合法的,得到答案

那么有哪些方案是不合法的呢?

显然,我们加入一条边形成了环的话,我们前面的计算会算上选择包含新加入的边的某个环的方案,而这个方案是不合法的

假设新加入的边是s->t,那么我们只要找到所有包含t->s的生成树,然后减掉,就得到了答案

具体而言,这里的操作方法,是在原来的DAG上面dp

$dp[t]$初始化为原来的答案,然后往$s$做拓扑序上的$dp$

转移方程是$dp[v]=\sum \frac{dp[u]}{in[u]}$,其中$in[u]$表示原图中的入度

显然只有能够在$t->s$路径上的点的$dp$值才有意义,而这里除掉度数表示的就是这里仅能选一条边

最后用原来答案减掉$dp[s]$即可

Code

#include<iostream>
#include<cstring> 
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cassert>
#define ll long long
#define MOD 1000000007
using namespace std;
inline int read(){
    int re=0,flag=1;char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9') re=(re<<1)+(re<<3)+ch-'0',ch=getchar();
    return re*flag;
}
int n,m,op,ed,first[100010],cnte;ll in[100010],inv[200010],re[100010];
struct edge{
    int to,next;
}a[200010];
inline void add(int u,int v){
    a[++cnte]=(edge){v,first[u]};first[u]=cnte;
}
ll ans=1,dp[100010];
int q[100010],head,tail;
void topo(){
    int i,u,v;head=tail=0;
    dp[ed]=ans;in[ed]--;
    for(i=1;i<=n;i++){
        if(!in[i]) q[tail++]=i;
        re[i]=in[i];
    }
    in[ed]++;
    while(head<tail){
        u=q[head++];dp[u]=dp[u]*inv[in[u]]%MOD;
        for(i=first[u];~i;i=a[i].next){
            v=a[i].to;re[v]--;
            dp[v]=(dp[v]+dp[u])%MOD;
            if(!re[v]) q[tail++]=v;
        }
    }
}
int main(){
    memset(first,-1,sizeof(first));
    n=read();m=read();op=read();ed=read();in[ed]++;
    int i,t1,t2;
    inv[1]=1;
    for(i=2;i<=m;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
    for(i=1;i<=m;i++){
        t1=read();t2=read();
        add(t1,t2);in[t2]++;
    }
    for(i=2;i<=n;i++) ans=ans*in[i]%MOD;
    if(ed==1){
        printf("%lld\n",ans);return 0;
    }
    topo();
    printf("%lld\n",(ans-dp[op]+MOD)%MOD);
}
posted @ 2018-09-07 10:35  dedicatus545  阅读(82)  评论(0编辑  收藏  举报