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

題目鏈接: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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章