bzoj4011: [HNOI2015]落忆枫音

18 篇文章 0 订阅
3 篇文章 0 订阅

题目链接

bzoj4011

题目描述

【问题描述】
不妨假设枫叶上有 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。

题解

若不加x至y的边,图为一个有向无环图。树形图的个数即为 ni=2d[i],d[i] 为i号点的入度。加上那条边后就会产生环。我们任按上述方法计算,但是会有一些不合法的方案,及将某些环选了进来。我们只要减去不合法的方案。注意到只有y至x的路径上的点再加入x到y这条边后才会属于某个环。那我们枚举环,计算包含这些环的方案数,就是枚举y到x的路径。那么不合法的方案就是: SyxiSd[i]
这样就可以DP了。记 f[i]=SyijSd[j]
不合法的方案就是 f[x] 。转移就是: f[i]=j>if[j]d[i]
初值是: f[y]=ni=2d[i]d[y]
拓扑排序一下就可以转移了。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;

#define N 100010
#define P 1000000007
queue<int>q;
int x,y,u,v,n,m,tot,ans,first[N],p[N*2],du[N],tmp[N],f[N];
struct edge{
    int x,next;
}e[N*2];

char BUF[1000001],*buf,*end;
#define getch() (buf==end?fread(BUF,1,1000000,stdin),buf=BUF,end=buf+1000000,*(buf++):*(buf++))
inline void read(int &x){
    static char c;
    for(c=getch();c<'0'||c>'9';c=getch());
    for(x=0;'0'<=c&&c<='9';c=getch())x=x*10+c-'0';
}

inline void add(int x,int y){
    e[++tot].x=y;
    e[tot].next=first[x];
    first[x]=tot;
}
int main(){
    read(n),read(m),read(x),read(y);
    for(int i=1;i<=m;i++){
        read(u),read(v);
        add(u,v); du[v]++; tmp[v]++;
    }
    ans=1;
    du[y]++;
    for(int i=2;i<=n;i++) ans=(long long)ans*du[i]%P;
    if(y==1){printf("%d",ans); return 0;}
    p[1]=1;
    for(int i=2;i<=m+1;i++) p[i]=(long long)(P-P/i)*p[P%i]%P;
    f[y]=ans;
    for(int i=1;i<=n;i++)
    if(!tmp[i]) q.push(i);
    while(!q.empty()){
        u=q.front(); q.pop();
        f[u]=(long long)f[u]*p[du[u]]%P;
        for(int i=first[u];i;i=e[i].next){
            f[e[i].x]+=f[u];
            if(f[e[i].x]>=P)f[e[i].x]-=P;
            tmp[e[i].x]--;
            if(!tmp[e[i].x]) q.push(e[i].x);
        }
    }
    printf("%d",(ans-f[x]+P)%P);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值