【BZOJ4011】【HNOI2015】落忆枫音

Description

不妨假设枫叶上有 n个穴位,穴位的编号为 1 ~  n。有若干条有向的脉络连接着这些穴位。穴位和脉络组成一个有向无环图——称之为脉络图(例如图 1),穴位的编号使得穴位 1 没有从其他穴位连向它的脉络,即穴位 1 只有连出去的脉络;由上面的故事可知,这个有向无环图存在一个树形子图,它是以穴位 1为根的包含全部n个穴位的一棵树——称之为脉络树(例如图 2和图 3给出的树都是图1给出的脉络图的子图);值得注意的是,脉络图中的脉络树方案可能有多种可能性,例如图2和图 3就是图 1给出的脉络图的两个脉络树方案。 
       
脉络树的形式化定义为:以穴位 r 为根的脉络树由枫叶上全部 n个穴位以及 n-  1 条脉络组成,脉络树里没有环,亦不存在从一个穴位连向自身的脉络,且对于枫叶上的每个穴位 s,都存在一条唯一的包含于脉络树内的脉络路径,使得从穴位r 出发沿着这条路径可以到达穴位 s。 现在向脉络图添加一条与已有脉络不同的脉络(注意:连接 2个穴位但方向不同的脉络是不同的脉络,例如从穴位3到4的脉络与从4到3的脉络是不同的脉络,因此,图 1 中不能添加从 3 到 4 的脉络,但可添加从 4 到 3 的脉络),这条新脉络可以是从一个穴位连向自身的(例如,图 1 中可添加从 4 到 4 的脉络)。原脉络图添加这条新脉络后得到的新脉络图可能会出现脉络构成的环。 请你求出添加了这一条脉络之后的新脉络图的以穴位 1 为根的脉络树方案数。由于方案可能有太多太多,请输出方案数对 1,000,000,007 取模得到的结果。 

Input

输入文件的第一行包含四个整数 n、m、x和y,依次代表枫叶上的穴位数、脉络数,以及要添加的脉络是从穴位 x连向穴位y的。 接下来 m行,每行两个整数,由空格隔开,代表一条脉络。第 i 行的两个整数为ui和vi,代表第 i 条脉络是从穴位 ui连向穴位vi的。 

Output

 输出一行,为添加了从穴位 x连向穴位 y的脉络后,枫叶上以穴位 1 为根的脉络树的方案数对 1,000,000,007取模得到的结果。 

Sample Input

4 4 4 3
1 2
1 3
2 4
3 2

Sample Output

3

HINT

 对于所有测试数据,1 <= n <= 100000,n - 1 <= m <= min(200000, n(n – 1) / 2), 



1 <= x, y, ui, vi <= n。
题解
dp+拓扑。思路题。
先考虑一个简单的问题,如果不加这条新边,那么方案数是除了1号点之外所有点的入度相乘。现在加上这条新边,还这样乘完,会因为有环而多算。所以要减去这些多算的。(假设添加的边是s->t,那么对于原图中t->s的每一条路径.不在该路径上的点的入度的乘积的和就是成环的方案数.)我也不知道为什么,如果哪位大佬知道为什么,求教。。。
先假设明白了吧,这样用dp[i]表示t到i的路径数,由于要转移到j,所以要出去刚才的贡献。在mod意义下就是乘以它的逆元。。先线性求逆元。再拓扑dp一下就行了。神题
ni[i]=-(mod/i)*(ni[mod%i])%mod;

证明:强博客:http://blog.miskcoo.com/2014/09/linear-find-all-invert
这个做法实际上是这样的,首先 111(modp)
然后我们设 p=ki+r, r<i, 1<i<p

再将这个式子放到 modp

意义下就会得到

ki+r0(modp)

两边同时乘上 i1r1

就会得到

kr1+i1i1i10kr1pi(pmodi)1(modp)(modp)(modp) 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define ll long long
#define mod 1000000007
using namespace std;
const int maxn=200010;
queue<int>q;
int pre[maxn*2],other[maxn*2],last[maxn*2],num;
int ru[maxn*2],in[maxn*2];
ll ni[maxn*2],dp[maxn],ans;
int n,m,s,t;
void add(int x,int y){
    num++;
    pre[num]=last[x];
    last[x]=num;
    other[num]=y;
}
void niyuan(){
    ni[1]=1;
    for(int i=2;i<=m+1;i++)
    ni[i]=-(mod/i)*(ni[mod%i])%mod;
}
void tuopu(){
    for(int i=1;i<=n;i++)
    if(!in[i]) q.push(i);
    dp[t]=ans;
    while(!q.empty()){
        int u=q.front();
        q.pop();
        dp[u]=(dp[u]*ni[in[u]])%mod;
        for(int i=last[u];i;i=pre[i]){
            int v=other[i];
            ru[v]--;
            dp[v]=(dp[v]+dp[u])%mod;
            if(!ru[v]){
                q.push(v);
            }
        }
    }
}
int main(){
    int x,y;
    cin>>n>>m>>s>>t;
    niyuan();
    for(int i=1;i<=m;i++){
        cin>>x>>y;
        add(x,y);
        ru[y]++;in[y]++;
    }
    in[t]++;
    ans=1;
    for(int i=2;i<=n;i++){
        ans=(ans*in[i])%mod;
    }
    if(t==1){
        printf("%lld",ans);
        return 0;
    }
    tuopu();
    printf("%lld\n",(ans-dp[s]+mod)%mod);
    return 0;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值