CF1000G Two-Paths 題解
CF1000G Two-Paths 題解
題意
給定一顆樹,詢問一條以 u
,v
為起點與重點的路徑的 點權和-邊權和,
每條邊最多經過兩次,點權僅能算一次所能得到的最大值。
思路:換根DP
首先對於上面這個圖,我們可以發現整個路徑的特徵,邊的編號表示一種可能的遍歷順序:
1.(其實可以先遍歷 u
的子樹,只不過圖中未畫出。)
2.在 u
到 LCA
的路徑上的一點 x
,可以選擇遍歷完 x
的子樹後再往 x
的父親走。
3.到達了 LCA
後,可以選擇遍歷完 LCA
的子樹後再遍歷 LCA
向上的那一部分。
4.從 LCA
到 v
的路徑上一點 x
可以選擇遍歷完 x
除了向 v
的那一部分在向 v
於是設一個dp
方程 dp[x][0]
與 dp[x][1]
dp[x][0]
表示從 x
往下走最終回到 x
的最大價值。
dp[x][1]
表示從 x
隨便走(即以 x
為出發點的全域性最大值),最終回到 x
的最大價值。
PS:上文的遍歷是在最優選擇下的遍歷,並不是全部遍歷完的意思。
DP的轉移:
僅僅選擇子樹的話很簡單,設當前點為 u
, 出點為 v
。
如果當前 u
最優可以選擇 v
,一定是可以獲得的價值 >0 ,即 dp[v][0]-edge[i]-edge[i^1]
,edge[i]
表示這條邊,而edge[i^1]
就是這條邊的反邊。(因為過去還要回來,所以是這個代價)
於是方程也很簡單:dp[u][0]
的初始值首先設為 a[x]
然後 dp[x][0]+=max(0,dp[y][0]-edge[i]-edge[i^1])
也就是一個簡單的 Dfs
可以解決。
而 dp[u][1]
僅僅需要一個換根就解決了。
但是注意轉移時,因為 dp[u][1]
表示的是全域性,可能會對 dp[v][1]
產生重複計算,這個時候我們需要剪掉重複計算的部分,即 dp[v][0]
,當然,dp[v][0]
可能對 dp[u][1]
無貢獻。計算一下是否 dp[v][0]-edge[i]-edge[i^1]
是否>0即可,轉移時同樣計算 dp[u][1]
是否可以對它的產生貢獻,也就是除去重複的貢獻後再剪掉兩個邊的邊權,距離可以看程式碼。
兩個 Dfs
即可搞定,一次預處理,一次換根就可以了。
處理答案
首先發現 u->LCA->v
是必定經過的,所以預先處理出這個邊權,
設 h(u,v)
為 v
是否計入 u
的貢獻,即 \(\max(0,dp[v][0]-e_{u->v}-e_{v->u})\)
發現整個 u->LCA
的代價可以表示為 \(\sum_{i=1}^n dp[u_i][0]-h(fa_{u_i},u_i)\) (這個式子的意思就是選完路徑上每一個點的最優子樹然後剪掉重複貢獻。
同理的v->LCA
的代價也珂以用一個類似的式子表示,最後加上 \(dp[LCA][1]\) 再減去路徑邊權即可。
然後上面的那個式子可以用一個字首和優化掉。這樣就可以直接求出答案了。
用了樹剖求 LCA
,於是處理路徑的邊權和會包含在了樹剖中的 DFS1
裡面。
Code:
#include<bits/stdc++.h>
using namespace std;
template <typename T>
inline void read(T &x){
x=0;char ch=getchar();bool f=false;
while(!isdigit(ch)) f|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
x=f?-x:x;return;
}
template <typename T>
inline void print(T x){
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10^48);return;
}
#define int long long
const int N=3e5+3;
int head[N],to[N<<1],Next[N<<1],edge[N<<1],tot=1,n,a[N],q;
inline void Addedge(int u,int v,int w){
to[++tot]=v,edge[tot]=w,Next[tot]=head[u],head[u]=tot;
return;
}
//Tree&DP
int fa[N],siz[N],son[N],dep[N],up[N],down[N];
inline void Dfs1(int x,int f){
dep[x]=dep[f]+1,siz[x]=1,fa[x]=f;
for(register int i=head[x];i;i=Next[i]){
int y=to[i];if(y==f) continue;
down[y]=down[x]+edge[i],up[y]=up[x]+edge[i^1];
Dfs1(y,x);siz[x]+=siz[y];
son[x]=siz[son[x]]<siz[y] ? y :son[x];
}
return;
}
int top[N];
inline void Dfs2(int x){
top[x]=son[fa[x]]==x ? top[fa[x]] : x;
if(son[x]) Dfs2(son[x]);
for(register int i=head[x];i;i=Next[i]){
int y=to[i];
if(y==fa[x]||y==son[x]) continue;
Dfs2(y);
}
return;
}
inline int LCA(int u,int v){
while(top[u]!=top[v]){
if(dep[top[u]]>dep[top[v]]) u=fa[top[u]];
else v=fa[top[v]];
}
return dep[u]>dep[v] ? v: u;
}
//DP
int dp[N][2];
inline int Get(int v,int i){return dp[v][0]-edge[i]-edge[i^1];}
int g[N];
inline void Dfs(int x){
dp[x][0]=a[x];
for(register int i=head[x];i;i=Next[i]){
int y=to[i];
if(y==fa[x]) continue;
Dfs(y);
if(Get(y,i)>0) dp[x][0]+=Get(y,i);
}
return;
}
inline void Croot(int x){
for(register int i=head[x];i;i=Next[i]){
int y=to[i];
if(y==fa[x]) continue;
int tmp=0;if(Get(y,i)>0) tmp=Get(y,i);
if(dp[x][1]-tmp-edge[i]-edge[i^1]>0) dp[y][1]=dp[y][0]+dp[x][1]-tmp-edge[i]-edge[i^1];
else dp[y][1]=dp[y][0];
g[y]=g[x]+dp[y][0]-tmp;
Croot(y);
}
return;
}
signed main(){
read(n),read(q);
for(register int i=1;i<=n;++i) read(a[i]);
for(register int i=1;i<n;++i){
int x,y,z1,z2;read(x),read(y),read(z1),z2=z1;
Addedge(x,y,z1),Addedge(y,x,z2);
}
Dfs1(1,0),Dfs2(1);
Dfs(1);dp[1][1]=dp[1][0];Croot(1);
while(q--){
int u,v;read(u),read(v);
int l=LCA(u,v);
int ans1=up[u]-up[l]+down[v]-down[l],ans2=g[u]+g[v]-2*g[l]+dp[l][1];
print(ans2-ans1),putchar('\n');
}
return 0;
}