//---------------------------------------------------------------------------
本篇博客是19年9月份写的,因为改成了比赛题目,所以设置隐藏,现在重新发。
//----------------------------------------------------------------------------
题目链接:https://vjudge.net/problem/Gym-101484F
原题目给的是一棵完全二叉树,所以暴力就可以过。
这里假设它是一颗一般树,那么可以用dsu on tree+线段树用O(N*logN*logN)的时间复杂度解决,或者用长链剖分+线段树+前缀最大值+后缀最大值做到O(N*logN)的时间复杂度。
dsu on tree 做法如下:
线段树维护当前每层的剩余点权和,然后启发式合并。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define mp make_pair
#define pb push_back
#define ls (o<<1)
#define rs (o<<1|1)
#define mid (l+r>>1)
#define ll long long
using namespace std;
const int N = 1e5+1000;
int n,m,val[N],s[N];
//-------------
int T[4*N];
void build(int o,int l,int r) {
T[o] = 0;
if(l==r) return;
build(ls,l,mid);
build(rs,mid+1,r);
}
void add(int o,int l,int r,int pos,int x) {
if(l==r) {
T[o] += x;
return;
}
if(mid>=pos) add(ls,l,mid,pos,x);
else add(rs,mid+1,r,pos,x);
T[o] = max(T[ls],T[rs]);
}
//------------
vector<int>nxt[N];
int siz[N],son[N],deep[N];
void split(int u,int f,int d) {
son[u] = 0;
siz[u] = 1;
deep[u] = d;
add(1,1,n,d,val[u]);
for(auto v:nxt[u]) {
if(v==f)continue;
split(v,u,d+1);
siz[u] += siz[v];
if(siz[son[u]]<siz[v]) son[u] = v;
}
}
//---------------------
int ans[N],Son;
void add(int u,int f,int data) {
add(1,1,n,deep[u],data*val[u]);
for(auto v:nxt[u]) {
if(v==f||v==Son) continue;
add(v,u,data);
}
}
void dfs(int u,int f,int opt) {
for(auto v:nxt[u]) {
if(v==f||v==son[u]) continue;
dfs(v,u,0); //计算轻儿子的答案,不保留贡献
}
if(son[u]) dfs(son[u],u,1),Son = son[u]; //计算重儿子的答案,保留贡献
add(u,f,-1),Son = 0; //遍历所有轻儿子,将贡献合并
ans[u] = T[1]; //记录答案
if(!opt) add(u,f,1); //如果u点是轻儿子,消除全部影响,清空所有信息
}
int main() {
//freopen("a.txt","r",stdin);
ios::sync_with_stdio(0);
cin>>n>>m>>val[1];
rep(i, 1, n-1) {
int u,v;
cin>>u>>v;
cin>>val[u];
nxt[v].pb(u);
}
build(1,1,n);
split(1,0,1);
dfs(1,0,0);
rep(i, 1, m) {
int x;
cin>>x;
cout<<ans[x]<<endl;
}
return 0;
}
长链剖分做法如下:
将整棵树的层分为3部分,第一部分是当前子树上面的层,第二部分是当前子树所在的层,第三部分是当前子树以下的层。
然后三部分取max,作为删除当前子树的答案。
pre维护层数的前缀最大值。
suf维护层数的后缀最大值。
dp维护子树内不同深度的点权和。
线段树维护整棵树中当前子树所在的各层点权剩余值。
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
#define per(i, a, b) for(int i = (a); i >= (b); i--)
#define mp make_pair
#define pb push_back
#define ls (o<<1)
#define rs (o<<1|1)
#define mid (l+r>>1)
#define ll long long
using namespace std;
const int N = 1e5+1000;
int n,m;
int T[4*N];
void build(int o,int l,int r) {
T[o] = 0;
if(l==r) return;
build(ls,l,mid);
build(rs,mid+1,r);
}
void add(int o,int l,int r,int pos,int x) {
if(l==r) {
T[o] += x;
return;
}
if(mid>=pos) add(ls,l,mid,pos,x);
else add(rs,mid+1,r,pos,x);
T[o] = max(T[ls],T[rs]);
}
int query(int o,int l,int r,int h,int t){
if(l>=h&&r<=t) return T[o];
int ans=0;
if(mid>=h) ans=max(ans,query(ls,l,mid,h,t));
if(mid<t) ans=max(ans,query(rs,mid+1,r,h,t));
return ans;
}
//-------------------
vector<int>nxt[N];
int son[N],len[N],val[N],deep[N],sum[N],pre[N],suf[N];
void split(int u,int f,int d) {
son[u] = 0;
len[u] = 1;
sum[d] += val[u];
// maxx[d] = max(sum[d],maxx[d-1]);
deep[u] = d;
for(auto v:nxt[u]) {
if(v==f)continue;
split(v,u,d+1);
if(len[son[u]]<len[v]) son[u] = v,len[u] = len[v]+1;
}
}
//-----------------------
int pos[N],dp[N],ans[N],cnt;
void dfs(int u,int f) {
pos[u] = ++cnt;
add(1,1,n,pos[u],sum[deep[u]]-val[u]);
dp[pos[u]] = val[u];
if(son[u]) dfs(son[u],u);
for(auto v:nxt[u]) {
if(v==f||v==son[u]) continue;
dfs(v,u);
rep(j, 0, len[v]-1) {
dp[pos[u]+j+1] += dp[pos[v]+j];
add(1,1,n,pos[u]+j+1,-dp[pos[v]+j]);
}
}
ans[u] = max(ans[u],query(1,1,n,pos[u],pos[u]+len[u]-1));
ans[u] = max(ans[u],pre[deep[u]-1]);
ans[u] = max(ans[u],suf[deep[u]+len[u]]);
}
int main() {
// freopen("a.txt","r",stdin);
ios::sync_with_stdio(0);
cin>>n>>m>>val[1];
rep(i, 1, n-1) {
int u,v;
cin>>u>>v;
nxt[v].pb(u);
cin>>val[u];
}
build(1,1,n);
split(1,0,1);
rep(i, 1, n) pre[i] = max(pre[i-1],sum[i]);
per(i, n, 1) suf[i] = max(suf[i+1],sum[i]);
dfs(1,0);
rep(i, 1, m) {
int x;
cin>>x;
cout<<ans[x]<<endl;
}
return 0;
}