題目鏈接:http://www.lydsy.com/JudgeOnline/problem.php?id=4011
題意概述:給出一張N點的DAG(從1可以到達所有的點),點1的入度爲0。現在加一條原圖沒有的邊,問有多少種方案可使這張圖變成一棵以1爲根的有向樹(即每個點的父親指向自己)。
N<=100000,M<=min(200000,N(N-1)/2).
實際上這個題主要在分析(感覺終於開始自己做出省選題了)。
先看沒有加邊的情況,yy一下你發現這種情況的答案就是所有rd(入度)不爲0的點rd相乘。道理是隻要給每個點指定一個父親,由於原圖是DAG,相當於逆着邊走,由於題目有保證1可以到達每個點,所以每個點一定可以反着走到1。
加邊的情況?注意到加邊之後可能還是DAG,一樣的處理。如果不是DAG說明有環。答案分成兩部分,不用新加的邊的方案+用新加的邊的合法方案。新加的邊的合法方案又等於新加邊所有方案-不合法方案(所有方案指的是父親亂指,不合法方案指的是指出了環)。
重點在於計算不合法方案數。來分析一下環的性質,可以發現新加的邊一定在環上,且我們已經計算的方案中任意一個點的rd爲1,這種情況下如果圖不連通可以有很多個環,但是所有環一定經過新加的邊所以只有一個環。於是暴力地我們可以枚舉所有的環,把環上所有點的rd變成1,其它所有點的rd相乘,得到的方案數就是這個環對當前答案的不合法貢獻,減掉。
這個算法隨便一卡就成了O(?反正是個指數級別) 的優秀算法了,怎麼優化呢?令加的邊爲x->y,可以發現任意一個環一定是從y出發經過一些點走到x經過x->y這條邊回到y,也就是說環的數量就是原圖中y到x的路徑數量。注意到所有點的rd之積mul是不變的,在暴力算法中每條環上的點rd變成1,也就對應y到x的路徑上的每一個點rd變成1,如果y到x的一條路徑上的rd乘積爲_mul,那麼這條路徑對不合法答案的貢獻就是mul/_mul,最後是所有的不合法貢獻相加之後從答案中扣除,所以可以設計出這樣的dp方程:
令f(i)表示i到x的路徑上的點對答案的不合法貢獻數,f(i)=sum{ f(j) | j->i } / rd[i]。(實際上這個dp就是對暴力的一個優化而已)
特殊判斷一些情況即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<map>
#include<vector>
#include<cctype>
using namespace std;
const int maxn=100005;
const int maxm=200005;
const int mo=1000000007;
int N,M,X,Y;
struct edge{ int to,next; }E[maxm];
int first[maxn],np,rd[maxn],sccno[maxn],sccsz[maxn],scc_cnt,dfs_clock,dfn[maxn],low[maxn];
int stk[maxn],top,inv[maxn],f[maxn],ID,mul;
void add_edge(int u,int v)
{
E[++np]=(edge){v,first[u]};
first[u]=np;
}
void data_in()
{
scanf("%d%d%d%d",&N,&M,&X,&Y);
int x,y;
for(int i=1;i<=M;i++){
scanf("%d%d",&x,&y);
rd[y]++;
add_edge(x,y);
}
add_edge(X,Y);
inv[1]=1;
for(int i=2;i<=N;i++)
inv[i]=1ll*inv[mo%i]*(mo-mo/i)%mo;
}
void tarjan_scc(int i)
{
low[i]=dfn[i]=++dfs_clock;
stk[++top]=i;
for(int p=first[i];p;p=E[p].next){
int j=E[p].to;
if(dfn[j]){
if(!sccno[j]) low[i]=min(low[i],dfn[j]);
continue;
}
tarjan_scc(j);
low[i]=min(low[i],low[j]);
}
if(low[i]==dfn[i]){
scc_cnt++;
while(1){
sccno[stk[top]]=scc_cnt;
sccsz[scc_cnt]++;
if(stk[top--]==i) break;
}
}
}
int dp(int i)
{
if(f[i]) return f[i];
if(i==X) return f[i]=mul;
for(int p=first[i];p;p=E[p].next){
int j=E[p].to;
if(sccno[j]!=ID||i==X&&j==Y) continue;
f[i]=(f[i]+dp(j))%mo;
}
return f[i]=1ll*f[i]*inv[rd[i]]%mo;
}
void work()
{
int ans=1;
for(int i=2;i<=N;i++) ans=1ll*ans*rd[i]%mo;
if(Y!=1){
ans=1ll*ans*inv[rd[Y]]%mo*(rd[Y]+1)%mo;
tarjan_scc(1);
int MAX=0;
for(int i=1;i<=N;i++)
if(sccsz[sccno[i]]>MAX) MAX=sccsz[sccno[i]],ID=sccno[i];
if(MAX>1){
rd[X]=rd[Y]=mul=1;
for(int i=2;i<=N;i++) mul=1ll*mul*rd[i]%mo;
ans=(ans-dp(Y)+mo)%mo;
}
}
printf("%d\n",ans);
}
int main()
{
data_in();
work();
return 0;
}