我最近越来越感觉到我弱爆了。
今天下午全机房做hnoiD2,但是我只会敲暴力……第二题看着像点分治,可是我不会写~ ~ ~
看来多做题确实是真理 ~ ~ ~
这道题精妙极了!
引用一段PoPoQQQ大神的话:
由朱刘算法的推论可知,如果除根节点外每个点都选择一条入边,由于没有环,因此一定会形成一个树形图;
因此答案就是
∏ni=2=2×degreei
,其中degreei表示第i个点的入度。
引用完毕
这样加了一条从x到y的边后如果继续用这个公式的话会多算一些不合法的情况,即存在环的情况。我们考虑一下怎么干掉它。
首先明显最多只会出现一个环。这个环是由原来存在的从y到x的一条路径与现在加上的从x到y的边相连接形成的。
除去这个环,剩下的还是一棵树(形图)。
那么不合法的方案实际上是许许多多环+树(仙人掌?我只是吐槽……)
设
S为y→x
的某条路径上的点的集合,那么非法方案的总数是
∑S∏2≤i≤n且i∉Sdegreei
而这个是可以运用动态规划来解决的。
设
f[i]
为
∑S是原图中y→i的一条路径的点集∏2≤j≤n,j∉Sdegreej
那么
f[i]=∑j→if[j]degreei
边界为
f[y]=∏2≤i≤ndegreeidegreey
注意如果y等于1号根节点或者出现自环,那么对原来答案没有影响。
除法转化为逆元,拓扑排序进行动态规划即可。
code:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn=100001;
const int maxm=200010;
const ll MOD=1e9+7;
ll inv[maxn];
int in[maxn],d[maxn];
ll f[maxn];
ll ans,n,m,x,y;
struct E{
int to,next;
}e[maxm];
int tot,list[maxn];
inline void add(int a,int b){
e[++tot]=(E){b,list[a]};list[a]=tot;
}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-48,ch=getchar();
return x;
}
void LinearShaker(int n){
inv[1]=1;
for(int i=2;i<=n;++i)
inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
}
void toposort(){
static int q[maxn];
int head=0,tail=0;
for(int i=1;i<=n;++i) if(!in[i]) q[++tail]=i;
while(head<tail){
int h=q[++head];
for(int k=list[h];k;k=e[k].next){
if(!--in[e[k].to]) q[++tail]=e[k].to;
}
}
f[y]=ans;
for(int i=1;i<=tail;++i){
f[q[i]]=(f[q[i]]*inv[d[q[i]]])%MOD;
for(int k=list[q[i]];k;k=e[k].next){
(f[e[k].to]+=f[q[i]])%=MOD;
}
}
}
int main(){
cin>>n>>m>>x>>y;
for(int i=1;i<=m;++i){
int u=read(),v=read();
add(u,v);
in[v]++;
}
memcpy(d,in,sizeof d);
d[y]++;
LinearShaker(n);
ans=1;
for(int i=2;i<=n;++i) ans=(ans*d[i])%MOD;
if(y==1||x==y){
cout<<ans<<"\n";
return 0;
}
toposort();
cout<<((ans-f[x])%MOD+MOD)%MOD<<"\n";
return 0;
}