题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=4011
题意概述:给出一张N点的DAG(连通),点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(N^2)的优秀算法了,怎么优化呢?令加的边为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就是对暴力的一个优化而已)
特殊判断一些情况即可。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cstdlib> 5 #include<algorithm> 6 #include<cmath> 7 #include<queue> 8 #include<set> 9 #include<map> 10 #include<vector> 11 #include<cctype> 12 using namespace std; 13 const int maxn=100005; 14 const int maxm=200005; 15 const int mo=1000000007; 16 17 int N,M,X,Y; 18 struct edge{ int to,next; }E[maxm]; 19 int first[maxn],np,rd[maxn],sccno[maxn],sccsz[maxn],scc_cnt,dfs_clock,dfn[maxn],low[maxn]; 20 int stk[maxn],top,inv[maxn],f[maxn],ID,mul; 21 22 void add_edge(int u,int v) 23 { 24 E[++np]=(edge){v,first[u]}; 25 first[u]=np; 26 } 27 void data_in() 28 { 29 scanf("%d%d%d%d",&N,&M,&X,&Y); 30 int x,y; 31 for(int i=1;i<=M;i++){ 32 scanf("%d%d",&x,&y); 33 rd[y]++; 34 add_edge(x,y); 35 } 36 add_edge(X,Y); 37 inv[1]=1; 38 for(int i=2;i<=N;i++) 39 inv[i]=1ll*inv[mo%i]*(mo-mo/i)%mo; 40 } 41 void tarjan_scc(int i) 42 { 43 low[i]=dfn[i]=++dfs_clock; 44 stk[++top]=i; 45 for(int p=first[i];p;p=E[p].next){ 46 int j=E[p].to; 47 if(dfn[j]){ 48 if(!sccno[j]) low[i]=min(low[i],dfn[j]); 49 continue; 50 } 51 tarjan_scc(j); 52 low[i]=min(low[i],low[j]); 53 } 54 if(low[i]==dfn[i]){ 55 scc_cnt++; 56 while(1){ 57 sccno[stk[top]]=scc_cnt; 58 sccsz[scc_cnt]++; 59 if(stk[top--]==i) break; 60 } 61 } 62 } 63 int dp(int i) 64 { 65 if(f[i]) return f[i]; 66 if(i==X) return f[i]=mul; 67 for(int p=first[i];p;p=E[p].next){ 68 int j=E[p].to; 69 if(sccno[j]!=ID||i==X&&j==Y) continue; 70 f[i]=(f[i]+dp(j))%mo; 71 } 72 return f[i]=1ll*f[i]*inv[rd[i]]%mo; 73 } 74 void work() 75 { 76 int ans=1; 77 for(int i=2;i<=N;i++) ans=1ll*ans*rd[i]%mo; 78 if(Y!=1){ 79 ans=1ll*ans*inv[rd[Y]]%mo*(rd[Y]+1)%mo; 80 tarjan_scc(1); 81 int MAX=0; 82 for(int i=1;i<=N;i++) 83 if(sccsz[sccno[i]]>MAX) MAX=sccsz[sccno[i]],ID=sccno[i]; 84 if(MAX>1){ 85 rd[X]=rd[Y]=mul=1; 86 for(int i=2;i<=N;i++) mul=1ll*mul*rd[i]%mo; 87 ans=(ans-dp(Y)+mo)%mo; 88 } 89 } 90 printf("%d\n",ans); 91 } 92 int main() 93 { 94 data_in(); 95 work(); 96 return 0; 97 }