競賽題解 - NOIP2018 保衛王國
\(\mathcal{NOIP2018}\) 保衛王國 - 競賽題解
按某一個炒雞dalao名曰 taotao 的話說:
\(\ \ \ \ \ \ \ \ \ “一道sb倍增題”\)
順便提一下他的〔題解〕(因為按照這個思路寫的,所以程式碼看起來也差不多)
因為比較復(胡)雜(炸),可能需要理解久一點
『題目』
參見 〔洛谷 P5024〕
『解析』
一、初步思考
如果不考慮多次詢問的話,顯然可以進行一次簡單的樹形DP,特殊判斷一下當前的點(也就是城市)能不能選(放軍隊)就行了~ 但是顯然會\(TLE\)。
由於\(m\)也就是詢問次數非常大,而dp狀態非常之多,不可能 \(O(1)\)
(至於一般的樹形dp相信大家都會)
對這道題本身進行分析——假設現在被限制的點是 \(u\),\(v\) ,根節點是 \(1\),那麼:
① \(lca\) 到 點\(1\) 的路徑上的 \(dp\) 值被 \(u\),\(v\) 共同影響;
② \(u\) 到 \(lca\) 的路徑上的 \(dp\)
③ \(v\) 到 \(lca\) 的路徑上的 \(dp\) 值被 \(v\) 影響;
恰好我們知道有一種倍增優化dp的演算法,所以正解順理成章就是樹上倍增優化樹形dp~(出題人的思維還是非常厲害的)
對於倍增求lca的運用不止可以求出lca,還經常用來統計兩點經過lca的簡單路徑上的一些特殊值,其轉移方法大多數是:
如果要求 u 到 u的第\(2^i\)個祖先 的路徑上的值,就綜合 u 到 u的第\(2^{i-1}\)個祖先 的路徑上的值,和 u的第\(2^{i-1}\)個祖先 到 u的第\(2^i\)個祖先 的路徑上的值;
二、倍增推導dp陣列
那麼對樹形dp的優化也是這樣的~因為一般的dp狀態定義是 “dp[u][0/1] 表示u選/不選時以u為根的子樹的最小花費”,那麼我們定義 \(fdp[u][k][0/1][0/1]\) 表示 “u選/不選(第三維)”並且 u的第\(2^i\)個祖先 選/不選(第四維)時,以 u的第\(2^i\)個祖先 為根的子樹的最小花費。另外幾個定義如下:
- \(fa[u][i]\) 表示“點u的第\(2^i\)個祖先”,如果沒有則值為0;
- \(dep[u]\) 表示“點u的深度”;
一次DFS就可以求出 dp,dep 的值(DFS1)。
因為我們考慮了2個點的取捨,必然就會有一些之前的(沒有任何限制時的最優值)是不合法的,所以我們需要對 fdp 進行一些修改。這就進入了第2次DFS:
根據 taotao 的說法,大概就是“合法的值=可能不合法的值-原有貢獻+合法的貢獻”
倍增求lca的基本操作——先把 u 到 u的父親 的值處理出來,然後依次處理出 fa和fdp 。也就是說我們先要處理 \(fdp[u][0][0/1][0/1]\) ,以下簡稱pre為u的父親:
- fdp[u][0][0/1][0/1]=INF :因為 u 和 pre 至少有一個要選;
- fdp[u][0][1][0]=dp[pre][0] :因為如果pre不選那麼u一定選;
- fdp[u][0][0][1]=dp[pre][1]-min(dp[u][0],dp[u][1])+dp[u][0]:因為選pre時,u的貢獻為min(dp[u][0],dp[u][1]),但是我們必須限制u不選,所以減去它原有貢獻再加上dp[u][0]也就是合法貢獻;
- fdp[u][0][1][1]=dp[pre][1]-min(dp[u][0],dp[u][1])+dp[u][1]:其他的都和上面的那種情況是相同的,只不過這是限制了u必須選也就是必須產生dp[u][1]的貢獻。
重要的不是遞推式,是思路“合法的值=可能不合法的值-原有貢獻+合法的貢獻”——
假設我們現在要推導從u到u的第\(2^k\)個祖先的狀態,也就是fdp[u][k][a][b](a決定u選/不選,b決定u的第\(2^k\)個祖先選/不選)
那麼,我們可以根據已經得到的
\(\ \ \ \ \ \ \ ①\)u到u的第\(2^{k-1}\)個祖先(定義為anc)的狀態也就是fdp[u][k-1][a][0/1]
\(\ \ \ \ \ \ \ ②\)anc到anc的第\(2^{k-1}\)個祖先的狀態也就是fdp[anc][k-1][0/1][b]
可見anc有兩種狀態——選/不選,那麼我們定義這個狀態為c
① 那麼上面說的“可能不合法的值”是指fdp[anc][k-1][c][b],因為我們並不知道這個值所代表的狀態是否滿足u的狀態;
② “原有貢獻”也就是dp[anc][c],在這種狀態下,u的狀態是不受限制的;
③ “合法的貢獻”說的是fdp[u][k-1][a][c],這樣的話也就限制了u的狀態
綜上所述,我們可以得到轉移式:
\[ fdp[u][k][a][b]=\min\begin{cases} fdp[anc][k-1][0][b]-dp[anc][0]+fdp[u][k-1][a][0]\\ fdp[anc][k-1][1][b]-dp[anc][1]+fdp[u][k-1][a][1] \end{cases} \]
(感覺開始difficult了)
這樣的話我們就可以轉移了~這就是第二次DFS(DFS2)。
三、lca求解答案
接下來就是求LCA,順便求解答案的過程——類比之前的轉移,這裡的轉移要點也是“合法的值=可能不合法的值-原有貢獻+合法的貢獻”;只是說這裡的轉移可以大致分為3步(下面都假設u的深度大於v):
- 將u移動到與v深度相同的位置,移動的路徑就是u的狀態會影響的路徑;
- 將u,v同時移動到它們的lca,u,v各自移動的路徑分別受u,v的狀態的影響;
- 將lca移動到根節點,lca移動的路徑由u,v的狀態共同影響;
那麼我們設在限制下,當前的以u(這裡強調“當前”是因為u在不斷移動)為根的子樹的最小花費為 val[1](選擇u) 和 val[2](不選擇u);以及當前的以v為根的子樹的 val[3] 和 val[4]。然後在定義一個fval[1~4]分別對應val[1~4],方便轉移~
(1)將u移動到與v同層
這裡涉及到的變數有 u 對應的狀態 val[1],val[2],以及 u 將要移動到的位置對應的狀態 fval[1],fval[2]。
假設現在要將u移動到u的第\(2^i\)個祖先,顯然u的狀態可以選也可以不選,那麼定義a為u的狀態:
對於fval[1],這裡的“可能不合法的值”是fdp[u][i][a][1](因為不知道是否滿足限制),“原有貢獻”為dp[u][a],“合法貢獻”為val[a](u的狀態對應的val值);當然fval[2]也可以類比上面的方法。
(2)將u,v同時移動到lca
和(1)相同,只是多了v也要同時轉移~(也就是說還涉及到val[3],val[4])
但是有一種特殊情況——v是u的祖先,這樣的話根據v的限制,我們就需要捨去一個val值:
現在的val[1],val[2]也就是lca選/不選所對應的值,但是如果v就是lca,v就會對val產生限制,也就是說當v不能選時,表示選的val[1]就要被捨去,而當v必須選時,表示不選的val[2]就要被捨去。
(3)將lca移動到根節點
完全一樣了,這裡的lca就代替了u,val[1],val[2]分別指lca選/不選時以lca為根的子樹的花費。
最後得到答案就是min(val[1],val[2])
(感覺好難講懂啊 \(QwQ\),不如先看一看程式碼?)
『原始碼』
/*Lucky_Glass*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=100000;
const ll INF=(1ll<<60);
struct GRAPH{
struct NODE{
int to,nxt;
NODE(){}
NODE(int _to,int _nxt):to(_to),nxt(_nxt){}
}edg[N*2+5];
int edgtot,adj[N+5];
void Init(){
edgtot=0;
memset(adj,-1,sizeof adj);
}
void AddEdge(int u,int v){
edg[++edgtot]=NODE(v,adj[u]);
adj[u]=edgtot;
}
}grp;
int n,m;
int cst[N+5],dep[N+5],fa[N+5][23];
ll dp[N+5][2],fdp[N+5][23][2][2];
void DFS1(int u,int pre){
dep[u]=dep[pre]+1;
dp[u][0]=0;dp[u][1]=cst[u];
for(int it=grp.adj[u];it!=-1;it=grp.edg[it].nxt){
int v=grp.edg[it].to;
if(v==pre) continue;
DFS1(v,u);
dp[u][0]+=dp[v][1];
dp[u][1]+=min(dp[v][0],dp[v][1]);
}
}
void DFS2(int u,int pre){
fa[u][0]=pre;
fdp[u][0][0][0]=INF;
fdp[u][0][1][0]=dp[pre][0];
fdp[u][0][0][1]=dp[pre][1]-min(dp[u][0],dp[u][1])+dp[u][0];
fdp[u][0][1][1]=dp[pre][1]-min(dp[u][0],dp[u][1])+dp[u][1];
for(int i=1;i<20;i++){
int anc=fa[u][i-1];
fa[u][i]=fa[anc][i-1];
fdp[u][i][0][0]=min(fdp[anc][i-1][0][0]-dp[anc][0]+fdp[u][i-1][0][0],fdp[anc][i-1][1][0]-dp[anc][1]+fdp[u][i-1][0][1]); //u -> anc -> fa[anc][i-1]
fdp[u][i][1][0]=min(fdp[anc][i-1][0][0]-dp[anc][0]+fdp[u][i-1][1][0],fdp[anc][i-1][1][0]-dp[anc][1]+fdp[u][i-1][1][1]);
fdp[u][i][0][1]=min(fdp[anc][i-1][0][1]-dp[anc][0]+fdp[u][i-1][0][0],fdp[anc][i-1][1][1]-dp[anc][1]+fdp[u][i-1][0][1]);
fdp[u][i][1][1]=min(fdp[anc][i-1][0][1]-dp[anc][0]+fdp[u][i-1][1][0],fdp[anc][i-1][1][1]-dp[anc][1]+fdp[u][i-1][1][1]);
}
for(int it=grp.adj[u];it!=-1;it=grp.edg[it].nxt){
int v=grp.edg[it].to;
if(v==pre) continue;
DFS2(v,u);
}
}
ll LCA(int u,int v,int U,int V){
if(dep[u]<dep[v]) swap(u,v),swap(U,V);
ll val[5]={0,(U? dp[u][1]:INF),(U? INF:dp[u][0]),(V? dp[v][1]:INF),(V? INF:dp[v][0])};
ll fval[5];
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v]){
fval[1]=min(fdp[u][i][0][1]-dp[u][0]+val[2],fdp[u][i][1][1]-dp[u][1]+val[1]);
fval[2]=min(fdp[u][i][0][0]-dp[u][0]+val[2],fdp[u][i][1][0]-dp[u][1]+val[1]);
val[1]=fval[1];val[2]=fval[2];
u=fa[u][i];
}
int lca;
if(u==v){
if(V) val[2]=INF;
else val[1]=INF;
lca=v;
}
else{
for(int i=19;i>=0;i--)
if(fa[u][i]!=fa[v][i]){
fval[1]=min(fdp[u][i][0][1]-dp[u][0]+val[2],fdp[u][i][1][1]-dp[u][1]+val[1]);
fval[2]=min(fdp[u][i][0][0]-dp[u][0]+val[2],fdp[u][i][1][0]-dp[u][1]+val[1]);
fval[3]=min(fdp[v][i][0][1]-dp[v][0]+val[4],fdp[v][i][1][1]-dp[v][1]+val[3]);
fval[4]=min(fdp[v][i][0][0]-dp[v][0]+val[4],fdp[v][i][1][0]-dp[v][1]+val[3]);
val[1]=fval[1];val[2]=fval[2];val[3]=fval[3];val[4]=fval[4];
u=fa[u][i];v=fa[v][i];
}
lca=fa[u][0];
fval[1]=dp[lca][1]-min(dp[u][1],dp[u][0])-min(dp[v][1],dp[v][0])+min(val[1],val[2])+min(val[3],val[4]);
fval[2]=dp[lca][0]-dp[u][1]-dp[v][1]+val[1]+val[3];
val[1]=fval[1];val[2]=fval[2];
}
for(int i=19;i>=0;i--)
if(dep[fa[lca][i]]>=dep[1]){
fval[1]=min(fdp[lca][i][0][1]-dp[lca][0]+val[2],fdp[lca][i][1][1]-dp[lca][1]+val[1]);
fval[2]=min(fdp[lca][i][0][0]-dp[lca][0]+val[2],fdp[lca][i][1][0]-dp[lca][1]+val[1]);
val[1]=fval[1];val[2]=fval[2];
lca=fa[lca][i];
}
return min(val[1],val[2]);
}
int main(){
grp.Init();
scanf("%d%d%*s",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&cst[i]);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
grp.AddEdge(u,v);grp.AddEdge(v,u);
}
DFS1(1,0);
DFS2(1,0);
while(m--){
int u,v,U,V;scanf("%d%d%d%d",&u,&U,&v,&V);
if((fa[u][0]==v || fa[v][0]==u) && (!U && !V)){
printf("-1\n");
continue;
}
printf("%lld\n",LCA(u,v,U,V));
}
return 0;
}
\(\mathcal{The\ End}\)
\(\mathcal{Thanks\ For\ Reading!}\)
有什麼沒講清楚的就在郵箱 \(lucky\[email protected]\) 裡面問嘛~