UOJ284 快樂遊戲雞(樹上動態規劃問題、長鏈剖分+單調棧)
阿新 • • 發佈:2021-02-05
技術標籤:題解
Description
一棵 n 個點的有根樹,帶點權 wi。
從 s 出發,希望達到 t,每秒可以從當前點移動到某一個兒子。
有一個死亡次數,初始為 0。若在某個點 i(i != s, t) 時,死亡次數 ≤ wi,那麼死亡次數自增 1,並且立刻跳回到 s。
給出 q 組 s, t,求最短時間。
n, q ≤
3
×
1
0
5
3 \times 10^5
3×105
Solution
- 每次從點 s 出生到撞程式猿死亡跟前幾次是怎麼死的並沒有關係。所以對於每次 “從點 s 出生到撞程式猿死亡” 的過程都可以貪心選最近的點早死早超生。
- 產生一個樸素的想法:記
g
i
,
j
g_i,_j
- 這樣設計狀態的複雜度太大,發現 g 單調並且有大量狀態相同。影象的話大概長這樣:
- 優化:換個角度,考慮有多少次從出生到死亡的過程需要一秒,多少次需要兩秒等等。
- 具體來說,考慮記錄 f[i][j] 表示 i 的子樹中和 i 距離小於等於 j 的點的權值最大值。 這樣 f[i][j]−f[i][j−1] 就等於有多少次從出生到死亡需要 j 秒。
- 那麼對於一個詢問(s,t),答案就是
∑ i = 1 d = d e p [ p o s [ m x v a l ] ] − d e p [ s ] ( f [ s ] [ i ] − f [ s ] [ i − 1 ] ) ∗ i + d i s ( s , t ) \sum_{i=1}^{d=dep[pos[mxval]]-dep[s]}(f[s][i]-f[s][i-1])*i+dis(s,t) ∑i=1d=dep[pos[mxval]]−dep[s](f[s][i]−f[s][i−1])∗i+dis(s,t)
= f [ s ] [ d ] ∗ d − ∑ i = 1 d − 1 f [ s ] [ i ] + d i s ( s , t ) =f[s][d]*d-\sum_{i=1}^{d-1}f[s][i]+dis(s,t) - 所以,可以把詢問按照 s 掛在樹上,想辦法維護f,離線求解。
- 考慮如何維護f。因為它與深度有關,所以考慮長鏈剖分。
- 長鏈剖分完後有兩種合併鏈的方法:
- 法一:用一個點v去更新f,是區間對一個數取max的操作,但因為f單調遞增,所以可以二分出第一個大於 v 的位置,然後區間覆蓋就可以了。這個可以用線段樹實現。(推薦按照樹剖的DFS序建一棵線段樹,然後所有操作都可以在這一棵線段樹上做。)
時間複雜度 O ( ( n + q ) l o g n ) O((n+q)logn) O((n+q)logn)。 - 法二:若dep[j]<dep[k]且w[j]>w[k],那麼k點顯然可以不用維護了,發現刪掉類似k的點後我們就得到了一個單調棧!因此我們決定維護一個子樹內部按照深度排好序後對於 w 的單調棧。詢問直接二分就好。注意點:1.我們需要求單調棧從棧頂到棧底的字首和,但是不好維護,所以選擇維護字尾和。2.棧的合併實際上按照任意順序時間複雜度都是
O
(
n
)
O(n)
O(n),但是我們需要注意空間,為省空間,我們長鏈剖分之後按照DFS序分配空間即可。
時間複雜度 O ( n + q l o g n ) O(n+qlogn) O(n+qlogn)。
Code
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=3e5+5;
struct Edge{int v,nxt;}edge[N];
int n,m,w[N],cnt,head[N];
int fa[N][25],mxw[N][25],len[N],son[N],dep[N],dfn[N],ind;
struct Query{int y,id;};
vector<Query> d[N];
long long ans[N];
int L[N],R[N],tot;
long long sum[N];//sum維護字尾和
struct Stack{int dep,w;}stk[N],q[N];
//用stk來記錄單調棧,是為了省空間
//按dfs序分配空間即可保證每個點對應的棧使用的stk區間無重疊部分
void addedge(int u,int v){
edge[++cnt].v=v;edge[cnt].nxt=head[u];head[u]=cnt;
}
void dfs1(int u){
dep[u]=dep[fa[u][0]]+1;
mxw[u][0]=w[fa[u][0]];
for(int i=1;i<=20;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
mxw[u][i]=max(mxw[u][i-1],mxw[fa[u][i-1]][i-1]);
}
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
dfs1(v);
if(len[v]>len[son[u]]) son[u]=v;
}
len[u]=len[son[u]]+1;
}
int get_mxw(int x,int y){
int ret=0;
for(int i=20;i>=0;i--)
if(dep[fa[x][i]]>dep[y])
ret=max(ret,mxw[x][i]),x=fa[x][i];
return ret;
}
void add(int x,Stack a){//從x點的棧的左端加入新元素
//新加入元素的dep保證<=棧中元素的dep的最小值
while(L[x]<=R[x]&&stk[L[x]].w<=a.w) L[x]++;
if(L[x]>R[x]){
sum[--L[x]]=0;stk[L[x]]=a;
}
else{
if(stk[L[x]].dep>a.dep){
stk[--L[x]]=a;sum[L[x]]=sum[L[x]+1]+1ll*stk[L[x]+1].dep*(stk[L[x]+1].w-a.w);
}
}
}
void merge(int x,int y){
//往x點的棧加入y點的棧中的元素時仍要滿足x點的棧中的元素dep值從小到大排布
tot=0;
while(L[x]<=R[x]&&stk[L[x]].dep<=stk[R[y]].dep) q[++tot]=stk[L[x]++];
while(tot&&L[y]<=R[y])
if(q[tot].dep>=stk[R[y]].dep) add(x,q[tot--]);
else add(x,stk[R[y]--]);
while(tot) add(x,q[tot--]);
while(L[y]<=R[y]) add(x,stk[R[y]--]);
}
long long query(int u,int v){
int mx=get_mxw(v,u),l=L[u],r=R[u],mid;
while(l<=r){
mid=l+r>>1;
if(stk[mid].w<mx) l=mid+1;
else r=mid-1;
}
if(stk[L[u]].w<=mx)
return 1ll*sum[L[u]]- 1ll*sum[l] + 1ll*stk[L[u]].dep*stk[L[u]].w - 1ll*dep[u]*mx - 1ll*(stk[l].w-mx)*stk[l].dep;
else
return 1ll*mx*(stk[l].dep-dep[u]);
}
void dfs2(int u){
dfn[u]=++ind;
if(son[u]){
dfs2(son[u]);
L[u]=L[son[u]];R[u]=R[son[u]];
}
else{L[u]=ind;R[u]=ind-1;}
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(v==son[u]) continue;
dfs2(v);
merge(u,v);
}
for(int i=0;i<d[u].size();i++){
int v=d[u][i].y;
int id=d[u][i].id;
ans[id]=query(u,v)+dep[v]-dep[u];
}
add(u,(Stack){dep[u],w[u]});
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
for(int i=2;i<=n;i++){
scanf("%d",&fa[i][0]);
addedge(fa[i][0],i);
}
dfs1(1);
scanf("%d",&m);
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
d[u].push_back((Query){v,i});
}
dfs2(1);
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
return 0;
}
參考文章:
https://vfleaking.blog.uoj.ac/blog/2292
https://www.cnblogs.com/penth/p/9801945.html
https://blog.csdn.net/qq_42555009/article/details/100934540
https://blog.csdn.net/zxyoi_dreamer/article/details/101705010
https://blog.csdn.net/Mr_wuyongcong/article/details/111996460