1. 程式人生 > 其它 >UOJ284 快樂遊戲雞(樹上動態規劃問題、長鏈剖分+單調棧)

UOJ284 快樂遊戲雞(樹上動態規劃問題、長鏈剖分+單調棧)

技術標籤:題解

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
    gi,j
    為當前在 i,已死亡 j − 1 次,再死一次所需時間。若 s, t 路徑上最大點權為 W,那麼答案為 ∑ i = 1 W g s , i + d i s ( s , t ) \sum _{i=1}^W g_s,_i + dis(s, t) i=1Wgs,i+dis(s,t)
  • 這樣設計狀態的複雜度太大,發現 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][i1])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)
    =f[s][d]di=1d1f[s][i]+dis(s,t)
  • 所以,可以把詢問按照 s 掛在樹上,想辦法維護f,離線求解。
  • 考慮如何維護f。因為它與深度有關,所以考慮長鏈剖分。
  • 長鏈剖分完後有兩種合併鏈的方法:
  1. 法一:用一個點v去更新f,是區間對一個數取max的操作,但因為f單調遞增,所以可以二分出第一個大於 v 的位置,然後區間覆蓋就可以了。這個可以用線段樹實現。(推薦按照樹剖的DFS序建一棵線段樹,然後所有操作都可以在這一棵線段樹上做。)
    時間複雜度 O ( ( n + q ) l o g n ) O((n+q)logn) O((n+q)logn)
  2. 法二:若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