[bzoj3991] [洛谷P3320] [SDOI2015] 尋寶遊戲
Description
小B最近正在玩一個尋寶遊戲,這個遊戲的地圖中有 \(N\) 個村莊和 \(N-1\) 條道路,並且任何兩個村莊之間有且僅有一條路徑可達。遊戲開始時,玩家可以任意選擇一個村莊,瞬間轉移到這個村莊,然後可以任意在地圖的道路上行走,若走到某個村莊中有寶物,則視為找到該村莊內的寶物,直到找到所有寶物並返回到最初轉移到的村莊為止。小B 希望評測一下這個遊戲的難度,因此他需要知道玩家找到所有寶物需要行走的最短路程。但是這個遊戲中寶物經常變化,有時某個村莊中會突然出現寶物,有時某個村莊內的寶物會突然消失,因此小B需要不斷地更新數據,但是小B太懶了,不願意自己計算,因此他向你求助。為了簡化問題,我們認為最開始時所有村莊內均沒有寶物
Input
第一行,兩個整數 \(N\) 、\(M\) ,其中 \(M\) 為寶物的變動次數。
接下來的 \(N-1\) 行,每行三個整數 \(x\)、\(y\)、\(z\),表示村莊 \(x\)、\(y\) 之間有一條長度為 \(z\) 的道路。
接下來的 \(M\) 行,每行一個整數 \(t\),表示一個寶物變動的操作。若該操作前村莊 \(t\) 內沒有寶物,則操作後村莊內有寶物;若該操作前村莊 \(t\) 內有寶物,則操作後村莊內沒有寶物。
Output
\(M\) 行,每行一個整數,其中第 \(i\) 行的整數表示第 \(i\) 次操作之後玩家找到所有寶物需要行走的最短路程。若只有一個村莊內有寶物,或者所有村莊內都沒有寶物,則輸出0。
Sample Input
4 5
1 2 30
2 3 50
2 4 60
2
3
4
2
1
Sample Output
0
100
220
220
280
HINT
\(1 \leq N \leq100000\)
\(1 \leq M \leq 100000\)
對於全部的數據,\(1 \leq z \leq10^9\)
想法
由於題目中要求找到所有寶物後要返回起點,故走過的每條路都走了2遍。
如果我們把所有有寶物的村莊(不妨稱它們為關鍵節點)及它們的 \(lca\) 拎出來,單獨建一棵樹,我們要走的所有邊便是這棵樹上的所有邊。
比如下圖(藍色的為關鍵節點):
我們叫這棵樹”虛樹“。
讓我們先回顧一下虛樹的建樹過程:
現在原樹上 \(dfs\)
然後將關鍵點按 \(dfs序\) 排序,依次考慮
用一個棧維護根到當前點 \(p\) 的路徑
插入下一個點 \(q\) 時,根到 \(q\) 的路徑中,\(q\) 的父節點為 \(lca(p,q)\)
於是就在原來根到 \(p\) 的路徑中找合適的位置將 \(lca(p,q)\) (如果原路徑中有就不用)及 \(q\) 插進去就行了。
這其中關鍵一步是“按 \(dfs序\) 排序”
而在這道題中,虛樹的邊數的二倍 便是按 \(dfs序\) 排序後的相鄰的關鍵點之間的距離和(包括最後一個關鍵點與第一個關鍵點之間的距離)
(畫個圖就可理解了。。。每條邊相當於在“進入”和“離開”時各經過一次)
那麽我們只要對虛樹中排序後所有相鄰關鍵點求一遍距離,再加起來就行了。
但題目中還有修改,怎麽辦?
註意到每次修改只改一個點——如果這個點在虛樹中,刪掉它影響的只是虛樹中與它相鄰的兩個關鍵點;如果不在虛樹中,影響的也只是它插到虛樹中後與它相鄰的兩個點。
於是我們可以用一個 \(set\) 來維護虛樹中點的 \(dfs序\) ,每次修改只需在 \(set\) 中 \(O(logn)\) 尋找這個點的前驅後繼。
如果要把這個點加到虛樹中,\(ans\) 減去它相鄰兩個點之間的距離,加上它分別到相鄰兩個點的距離
如果要把這個點從虛樹中刪除,\(ans\) 減去它分別到相鄰兩個點的距離,加上它相鄰兩個點之間的距離
代碼
一直用 \(set\) 不是很熟練
這次 \(get\) 了新技能:
y=*--st.lower_bound(x); //找小於x的最大數
z=*st.upper_bound(x); //找大於x的最小數
還有一個小技巧是在 \(set\) 中先加入 \(inf 與 -inf\) ,避免出現奇怪問題
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<set>
#define INF 1000000
using namespace std;
typedef long long ll;
const int N = 100005;
struct node{
int v,len;
node *next;
}pool[N*2],*h[N];
int cnt;
void addedge(int u,int v,int l){
node *p=&pool[++cnt],*q=&pool[++cnt];
p->v=v;p->next=h[u];h[u]=p;p->len=l;
q->v=u;q->next=h[v];h[v]=q;q->len=l;
}
int n,m,vis[N];
int f[N][20],dep[N],dfn[N],re[N],tot;
ll sum[N];
void dfs(int u){
int v;
dfn[u]=++tot; re[tot]=u;
for(node *p=h[u];p;p=p->next)
if(!dep[v=p->v]){
dep[v]=dep[u]+1;
f[v][0]=u; sum[v]=sum[u]+p->len;
for(int j=1;j<20;j++)
f[v][j]=f[f[v][j-1]][j-1];
dfs(v);
}
}
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
for(int i=19;i>=0;i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
inline ll Sum(int x,int y) { return sum[x]+sum[y]-sum[lca(x,y)]*2; }
set<int> st;
int main()
{
int x,y,z;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z);
}
dep[1]=1;
dfs(1);
int s,p,q;
ll t=0;
st.insert(-INF); st.insert(INF);
while(m--){
scanf("%d",&x);
if(vis[x]==0){
vis[x]=1;
s=st.size();
if(s==2) { st.insert(dfn[x]); printf("0\n"); continue; }
p=*--st.lower_bound(dfn[x]); q=*st.upper_bound(dfn[x]);
if(p==-INF) p=*--(--st.end());
if(q==INF) q=*++st.begin();
p=re[p]; q=re[q];
t=t-Sum(p,q)+Sum(p,x)+Sum(x,q);
st.insert(dfn[x]);
}
else{
vis[x]=0;
s=st.size();
if(s==2) { st.erase(dfn[x]); printf("0\n"); continue; }
p=*--st.lower_bound(dfn[x]); q=*st.upper_bound(dfn[x]);
if(p==-INF) p=*--(--st.end());
if(q==INF) q=*++st.begin();
p=re[p]; q=re[q];
t=t+Sum(p,q)-Sum(p,x)-Sum(x,q);
st.erase(dfn[x]);
}
printf("%lld\n",t);
}
return 0;
}
[bzoj3991] [洛谷P3320] [SDOI2015] 尋寶遊戲