bzoj 1179 搶掠計劃atm (縮點+有向無環圖DP)
   手動博客搬家: 本文發表于20170716 10:58:18, 原地址https://blog.csdn.net/suncongbo/article/details/81061601
 https://www.lydsy.com/JudgeOnline/problem.php?id=1179
 題意:給定一張有向圖,點有點權。給定起點(僅一個)\(s\)與終點集合(多個)\(T\),問開始于起點、結束于終點集合中的一個點的路徑中點權和的最大值。
 題解:這里,第一步當然是tarjan縮點。
 由于是開始于特定點,dp有些麻煩,建議采用spfa. 但是DP也能做,而且bfs和dfs都能做。只不過,有一些需要注意的地方,很容易寫錯。
 bfs狀態轉移方程很簡單:\(dp[i]\)表示從s出發到i這個點路徑點權和的最大值,\(dp[i]=max_{j\in ind[i]}{dp[j]+a[i]}\), a[i]為點權。對于終點集合,我們可以建一個超級終點,每一個終點向超級終點連邊,當然也可以樸素地枚舉每個終點。但是,有一個細節問題坑了我很久:如果是這樣寫的:
 Tarjan原圖中的每一個點;
建新圖;
bfs(); (隊列中初始只有一個元素s)
ans = dp[超級終點]; 
則必然會WA. 后來我又對著標程查了好久,問了幾位大佬,最后實在沒辦法對拍了一下,發現生成了這樣一組數據成功卡掉自己,經簡化如下——
 3 2
1 2
3 2
5
1
3
1 1
2
Read 0,Expected 6. 
正確的寫法應該是:
 Tarjan(s); //從s能到達的點
建新圖;
bfs(); (隊列中初始只有一個元素s)
ans = dp[超級終點]; 
只有第一行tarjan不一樣。為什么呢?
 原因是:對于一個點i, 剛才我們討論的數食物鏈、字母最多出現次數、最長路徑,都是要求\(i\)的所有入邊的起點都被更新后才可轉移\(i\)(將其加入隊列),bfs的時候從所有入度為0的點開始。但是,現在情況變了,如果我們只從s開始bfs,有一些s走不到的點將永遠不會被更新,這樣這個點所連向的邊也不會被更新,就相當于這個點作廢了。但是實際上這個點并未作廢。如圖所示,如果1號點為起點,2號點為終點,那3號點顯然不會被訪問到,但是由于2號點有1->2, 3->2兩條入邊,因此3號點不更新,2號點永遠也不會被更新,答案一直是0.但是實際上,2還應該被1更新,因為起點1一定可以通過1->2進入2號點。
 實際上,我們確保這種入邊的起點先統計的順序,原因是防止一個點有許多條轉移的途徑,但是只枚舉了其中的一部分,最優解藏在另一部分中的這種情況。但是在這里,所謂“另一部分”是s點永遠不可能達到的,因此答案為0,不可能成為最優解,反而還會拖累2號點的更新。
 AC代碼:
 #include<cstdio>
#include<algorithm>
using namespace std;const int N = 500000;
struct Edge
{int v,nxt; bool us;
} e[N+2],e0[N+2];
int fe[N+2],fe0[N+2];
int a[N+2],a0[N+2];
bool f[N+2];
int dfn[N+2],low[N+2];
int sta[N+2];
bool ins[N+2];
int clr[N+2];
int dp[N+2];
int que[N+2];
int ind[N+2];
bool f0[N+2];
int n,m,m0,s,p,tp,cnt,num;void addedge(int u,int v)
{m++; e[m].v = v;e[m].nxt = fe[u]; fe[u] = m;
}void addedge0(int u,int v)
{m0++; e0[m0].v = v; ind[v]++;e0[m0].nxt = fe0[u]; fe0[u] = m0;
}void Tarjan(int u)
{cnt++; dfn[u] = low[u] = cnt; ins[u] = true;tp++; sta[tp] = u;for(int i=fe[u]; i; i=e[i].nxt){if(dfn[e[i].v]==0) {Tarjan(e[i].v); low[u] = min(low[u],low[e[i].v]);}else if(ins[e[i].v]==true) {low[u] = min(low[u],dfn[e[i].v]);}}if(low[u]==dfn[u]){num++; clr[u] = num; a0[num] = a[u]; f0[num] = f[u];while(sta[tp]!=u){ins[sta[tp]] = false;clr[sta[tp]] = num;a0[num] += a[sta[tp]];f0[num] = f[sta[tp]]||f0[num];tp--;}ins[u] = false; tp--;}
}void bfs()
{int head = 1,tail = 1; que[tail] = clr[s]; dp[clr[s]] = a0[clr[s]];while(head<=tail){int cur = que[head]; head++;for(int i=fe0[cur]; i; i=e0[i].nxt){if(e0[i].us==false){ind[e0[i].v]--; e0[i].us = true; dp[e0[i].v] = max(dp[e0[i].v],dp[cur]+a0[e0[i].v]);if(ind[e0[i].v]==0){tail++; que[tail] = e0[i].v;}}}}
}int main()
{int mm; m = 0; scanf("%d%d",&n,&mm);for(int i=1; i<=mm; i++){int x,y; scanf("%d%d",&x,&y); addedge(x,y);}for(int i=1; i<=n; i++) scanf("%d",&a[i]);scanf("%d%d",&s,&p);for(int i=1; i<=p; i++) {int x; scanf("%d",&x); f[x] = true;}cnt = 0; Tarjan(s);//for(int i=1; i<=n; i++) if(!dfn[i]) Tarjan(i);num++;for(int i=1; i<=n; i++){for(int j=fe[i]; j; j=e[j].nxt){if(clr[i]!=0 && clr[e[j].v]!=0 && clr[i]!=clr[e[j].v]){addedge0(clr[i],clr[e[j].v]);}}}for(int i=1; i<=num; i++) {if(f0[i]==true) addedge0(i,num);}bfs();printf("%d\n",dp[num]);return 0;
} 
dfs當然也能做,此處不再贅述。
            發表于 
2019-01-22 19:43 suncongbo 閱讀(
...) 評論() 編輯 收藏       
刷新評論刷新頁面返回頂部
                            
總結
                            
                                以上是生活随笔為你收集整理的bzoj 1179 抢掠计划atm (缩点+有向无环图DP)的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                            
                                如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。