圖論之tarjan縮點


縮點,就是把一張有向有環圖中的環縮成一個個點,形成一個有向無環圖。

首先我介紹一下為什么這題要縮點(有人肯定覺得這是放屁,這不就是縮點的模板題嗎?但我們不能這么想,考試的時候不會有人告訴你打什么板上去吧)

根據題目意思,我們只需要找出一條點權最大的路徑就行了,不限制點的個數。那么考慮對於一個環上的點被選擇了,一整條環是不是應該都被選擇,這一定很優,能選干嘛不選。很關鍵的是題目還允許我們重復經過某條邊或者某個點,我們就不需要考慮其他了。因此整個環實際上可以看成一個點(選了其中一個點就應該選其他的點)

那么就正式開始縮環為點了。當然了,首先肯定是找環,為大家推薦兩篇博客(不是我宣傳,這兩篇博客也只是我找的[] (http://blog.csdn.net/acmmmm/article/details/16361033))[](http://blog.csdn.net/sentimental_dog/article/details/53790582)

希望博客被我轉載的博主不要介意。

看看這兩篇博客,我覺得大家就有了一個基本認識了。在縮點操作中,最重要的是維護三個東西,它們在我代碼里分別是stac(棧)(ps:之所以不加k是因為萬能頭文件的荼毒),dfn(時間戳),low(夠追溯到的最早的棧中節點的次序號),詳細的解釋在代碼注釋里。

下面就是考慮對這三個東西的運用。詳細參考博客(博客帶圖),需要注意的是,當dfn[u]==low[u]時,表明u一定是環上的一點,且環上的其他點就是u的子樹。為什么呢?看代碼
low[x]=dfn[x]=++tim;

low[x]=min(low[x],low[v]);

我截取了兩句代碼,第一句是對點x的low,dfn的初始化。在之后的操作中,low[x]始終取自己子樹low[v]的較小值,那么什么情況會使得dfn[u]又“重新”和low[u]相等呢,就是在u的子樹中有一條邊(就是博客1中的后向邊)直接指回了u。這樣不就是形成了一個環了嗎?

之后就是把環上所有的點的sd都變成這個u,即用u代替整個環,並把權值集中在u上

還有值得注意的,這個棧表示的究竟是什么?(這個在博客1中也有),根據我的理解表示的是當前搜索的一條鏈上的一個個點吧。

下面我附上代碼先

#include<bits/stdc++.h>
using namespace std; const int maxn=10000+15; int n,m,sum,tim,top,s; int p[maxn],head[maxn],sd[maxn],dfn[maxn],low[maxn];//DFN(u)為節點u搜索被搜索到時的次序編號(時間戳),Low(u)為u或u的子樹能夠追溯到的最早的棧中節點的次序號 
int stac[maxn],vis[maxn];//棧只為了表示此時是否有父子關系 
int h[maxn],in[maxn],dist[maxn]; struct EDGE { int to;int next;int from; }edge[maxn*10],ed[maxn*10]; void add(int x,int y) { edge[++sum].next=head[x]; edge[sum].from=x; edge[sum].to=y; head[x]=sum; } void tarjan(int x) { low[x]=dfn[x]=++tim; stac[++top]=x;vis[x]=1; for (int i=head[x];i;i=edge[i].next) { int v=edge[i].to; if (!dfn[v]) { tarjan(v); low[x]=min(low[x],low[v]); } else if (vis[v]) { low[x]=min(low[x],low[v]); } } if (dfn[x]==low[x]) { int y; while (y=stac[top--]) { sd[y]=x; vis[y]=0; if (x==y) break; p[x]+=p[y]; } } } int topo() { queue <int> q; int tot=0; for (int i=1;i<=n;i++) if (sd[i]==i&&!in[i]) { q.push(i); dist[i]=p[i]; } while (!q.empty()) { int k=q.front();q.pop(); for (int i=h[k];i;i=ed[i].next) { int v=ed[i].to; dist[v]=max(dist[v],dist[k]+p[v]); in[v]--; if (in[v]==0) q.push(v); } } int ans=0; for (int i=1;i<=n;i++) ans=max(ans,dist[i]); return ans; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) scanf("%d",&p[i]); for (int i=1;i<=m;i++) { int u,v; scanf("%d%d",&u,&v); add(u,v); } for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i); for (int i=1;i<=m;i++) { int x=sd[edge[i].from],y=sd[edge[i].to]; if (x!=y) { ed[++s].next=h[x]; ed[s].to=y; ed[s].from=x; h[x]=s; in[y]++; } } printf("%d",topo()); return 0; }

在處理了環后,我們就重新建立一張圖,以每個環為節點(孤立一個點也算也算環的,其實也就是強聯通分量了)。在這張圖中我們要dp,顯然對於任意邊<u,v>,dp[v]=max(dp[v],dp[u]+p[v]),p[v]是v是這個環的總權值。

那么怎么解決無后效性問題呢?答案就是拓撲排序,至於為什么,在我的另一篇題解里我有提及。這下我有安利嫌疑了,但我還是希望大家去看一看,下面我附上鏈接。

這也是一篇題解,其實主要講的就是拓撲排序解決DP的無后效性問題了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM