【BZOJ - 3832】[Poi2014] Rally
[Poi2014] Rally
@[email protected]
給定一個N個點M條邊的有向無環圖,每條邊長度都是1。
請找到一個點,使得刪掉這個點後剩餘的圖中的最長路徑最短。
Input
第一行包含兩個正整數
,表示點數、邊數。
接下來M行每行包含兩個正整數
,表示A[i]到B[i]有一條邊。
Output
包含一行兩個整數x,y,用一個空格隔開,x為要刪去的點,y為刪除x後圖中的最長路徑的長度,如果有多組解請輸出任意一組。
Sample Input
6 5
1 3
1 4
3 6
3 4
4 5
Sample Output
1 2
@Solution - Part [email protected]
【先膜拜想出來這道題的大佬們 orz】
【太神了,要我一輩子都想不出來這種題QAQ】
首先處理圖不連通的情況:建立超級源點 s 向所有入度為 0 的點連邊,建立超級匯點 t 使所有出度為 0 的點向 t 連邊。
然後看一張圖:
紅線是該圖的一個“割”:將紅線上的邊刪除過後,原圖形成兩個連通塊且兩個連通塊分別包含源點 s 與匯點 t 。
我們令包含源點 s 的連通塊為集合 S,包含匯點 t 的為集合 T。
為什麼要畫這樣一條“割”呢?可以發現有這樣一個性質:原圖中 s 到 t 的每一條路徑恰好包含“割”上的一條邊。
反過來,我們可以令一條邊的 tag = “經過這條邊的路徑長度的最大值”,再用一個數據結構儲存“割”上的所有邊的 tag。
然後——假如我們要求解刪去某一條“割”邊的最長路,等價於在資料結構中刪除這條邊的 tag 後,求資料結構中的最大 tag。
刪去某一個點,等價於刪除它所有的入邊。
這個資料結構應該支援(1)加入與刪除(2)求最大值。
可以用堆,也可以像我一樣直接用 multiset【常數大但好寫www】。
也就是說:我們只需要使得“割”經過一個點的所有入邊,就可以在 O(log n) 時間內求解刪去某一個點的答案。
@Solution - Part [email protected]
具體來講,我們怎麼使得“割”經過一個點的入邊呢?
可以用拓撲序來進行這個過程。
我們按照拓撲序,依此遍歷每一個結點。
對於一個結點 i:先將它所有的入邊從“割”中刪除,求“割”中的最大值,再將它所有的出邊加入“割”。
為什麼這樣是對的?
首先:可以發現這樣操作以後,“割”仍然合法。
然後:因為是按照拓撲序進行的,所以 i 的入邊連線的點 j 在之前一定已經處理過了。i 的入邊作為 j 的出邊,在處理 j 的時候就會加入“割”,所以 i 的所有入邊此時肯定是在“割”內的。
所以這樣是正確的。
@Some [email protected]
怎麼求解一條邊(u, v)的 tag?
可以用 DAG 上 dp 處理出 s 到 u 的最長路與 v 到 t 的最長路。
記住 s, t 是虛點,所以要消除它們對答案的影響。
可能出現這種情況:刪除一個點所有的入邊後,s 和 t 不連通了!
這時候的最長路 = max(s到集合S中的點的最長路, 集合T中的點到t的最長路)
【還記得集合S和T嗎?不記得往上翻一翻】
再開兩個堆來維護即可。
@[email protected]
在 bzoj 上能過,但是在本地 T 得很慘。
建議大家還是寫一寫帶刪除的堆吧。
#include<set>
#include<queue>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 500000;
const int MAXM = 1000000;
const int INF = (1<<30);
int read() {
int x = 0, f = 1; char ch = getchar();
while( (ch > '9' || ch < '0') && ch != '-' ) ch = getchar();
if( ch == '-' ) {f = -1; ch = getchar();}
while( '0' <= ch && ch <= '9' ) {x = x*10 + ch-'0'; ch = getchar();}
return x * f;
}
struct edge{
int to;
edge *nxt;
}edges[2*MAXM + 5], *adj[MAXN + 5], *ecnt;
int n, m, idg[MAXN + 5], odg[MAXN + 5];
int tp[MAXN + 5], tcnt = 0;
vector<int>G[MAXN + 5];
multiset<int, greater<int> >S, T, E;
void init() {
ecnt = &edges[0];
for(int i=0;i<=n+1;i++) {
adj[i] = NULL;
G[i].clear();
idg[i] = odg[i] = 0;
}
S.clear(); T.clear(); E.clear();
}
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
G[v].push_back(u);
}
void tpsort() {
tcnt = 0;
for(int i=0;i<=n+1;i++)
idg[i] = 0;
for(int i=0;i<=n+1;i++)
for(edge *p=adj[i];p!=NULL;p=p->nxt)
idg[p->to]++;
queue<int>que; que.push(0);
while( !que.empty() ) {
int f = que.front(); que.pop(); tp[tcnt++] = f;
for(edge *p=adj[f];p!=NULL;p=p->nxt)
if( !(--idg[p->to]) ) que.push(p->to);
}
}
int f[MAXN + 5], g[MAXN + 5];
int GetAnswer() {
int ret = 0;
if( !S.empty() )
ret = max(ret, *S.begin());
if( !T.empty() )
ret = max(ret, *T.begin());
if( !E.empty() )
ret = max(ret, *E.begin());
return ret;
}
void solve() {
n = read(), m = read(); init();
for(int i=1;i<=m;i++) {
int u, v;
u = read(), v = read();
addedge(u, v);
idg[v]++; odg[u]++;
}
for(int i=1;i<=n;i++) {
if( !idg[i] ) addedge(0, i);
if( !odg[i] ) addedge(i, n+1);
}
tpsort(); f[0] = g[n+1] = 0;
for(int i=1;i<=n+1;i++) {
f[tp[i]] = 0;
for(int j=0;j<G[tp[i]].size();j++)
f[tp[i]] = max(f[tp[i]], f[G[tp[i]][j]] + 1);
}
for(int i=n;i>=0;i--) {
g[tp[i]] = 0;
for(edge *p=adj[tp[i]];p!=NULL;p=p->nxt)
g[tp[i]] = max(g[tp[i]], g[p->to] + 1);
}
for(int i=0;i<=n+1;i++)
T.insert(g[i]-1);
int ans = INF, num;
for(int i=0;i<=n+1;i++) {
for(int j=0;j<G[tp[i]].size();j++)
E.erase(E.find(f[G[tp[i]][j]]+g[tp[i]]-1));
T.erase(T.find(g[tp[i]]-1));
if( tp[i] != 0 && tp[i] != n+1 ) {
int x = GetAnswer();
if( x < ans ) ans = x, num = tp[i];
else if( x == ans && num > tp[i] ) num = tp[i];
}
for(edge *p=adj[tp[i]];p!=NULL;p=p->nxt)
E.insert(f[tp[i]]+g[p->to]-1);
S.insert(f[tp[i]]-1);
}
printf("%d %d\n", num, ans);
}
int main() {
solve();
}
@[email protected]
就是這樣,新的一天裡,也請多多關照哦(ノω<。)ノ))☆.。