題目描述:
大意:給出樹上的一些帶權值的鏈,選出一些點不相交的鏈,使得它們的權值和最大。
題目分析:
問題就是怎麼求刪掉這條鏈後的極大子樹的DP值。
大力數據結構可以重鏈剖分後在每個點上存下輕兒子的DP值之和,然後往上跳重鏈,每次加上這條重鏈上存的值(單點修改區間求和),減去上一次跳上來的輕兒子,加上當前點的重兒子。複雜度
對於這道題還有複雜度更低的做法:記表示的所有兒子的DP值之和,那麼對於一條以爲LCA,鏈端點爲的鏈,它對的貢獻就是這條鏈上的值之和減去鏈上除了點的值。
記,貢獻變爲鏈上除了點的值之和加上的所有兒子的值之和。
由於DP是從下往上的,一定會在鏈端點在它的子樹中時被統計,所以給的子樹直接加上,查詢時直接查詢鏈端點處的值即可(區間修改單點查詢),複雜度。
上面這種做法,在最後一步可以將子樹加換成並查集縮鏈並維護權值,複雜度更低,但是在換父親時有一點實現上的細節,詳見這篇博客
Code:
#include<bits/stdc++.h>
#define maxn 200005
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
template<class T>inline void read(T &a){
char c;while(!isdigit(c=getc()));
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const int Log = 17;
int n,m,F[Log+1][maxn],dep[maxn],in[maxn],out[maxn],tim,f[maxn];
int arr[maxn],fir[maxn],nxt[maxn];
inline void line(int x,int y){nxt[y]=fir[x],fir[x]=y;}
struct node{int x,y,w;};
vector<node>q[maxn];
inline int LCA(int u,int v){
if(dep[u]<dep[v]) swap(u,v);
for(int i=0,d=dep[u]-dep[v];d;d>>=1,i++) if(d&1) u=F[i][u];
if(u==v) return u;
for(int i=Log;i>=0;i--) if(F[i][u]!=F[i][v]) u=F[i][u],v=F[i][v];
return F[0][u];
}
void dfs(int u){in[u]=++tim;for(int i=fir[u];i;i=nxt[i]) dep[i]=dep[u]+1,dfs(i);out[u]=tim;}
inline void upd(int i,int v){for(;i<=n;i+=i&-i) arr[i]+=v;}
inline int qsum(int i){int s=0;for(;i;i-=i&-i) s+=arr[i];return s;}
int main()
{
freopen("1.in","r",stdin);
int x,y,w;
read(n),read(m);
for(int i=2;i<=n;i++) read(F[0][i]),line(F[0][i],i);
for(int j=1;j<=Log;j++)
for(int i=1;i<=n;i++)
F[j][i]=F[j-1][F[j-1][i]];
dfs(1);
while(m--){
read(x),read(y),read(w);
q[LCA(x,y)].push_back((node){x,y,w});
}
for(int u=n;u>=1;u--){
for(node t:q[u]) f[u]=max(f[u],qsum(in[t.x])+qsum(in[t.y])+t.w);
int s=0;
for(int v=fir[u];v;v=nxt[v]) s+=f[v];
f[u]+=s;
upd(in[u],s-f[u]),upd(out[u]+1,f[u]-s);
}
printf("%d\n",f[1]);
}