1. 程式人生 > >BZOJ 4011 HNOI2015 落憶楓音 DAG上的dp(實際上重點在於分析)

BZOJ 4011 HNOI2015 落憶楓音 DAG上的dp(實際上重點在於分析)

ora can main span next 暴力 pre -m type

題目鏈接: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 }

BZOJ 4011 HNOI2015 落憶楓音 DAG上的dp(實際上重點在於分析)