LOJ #2497 Banany 解题报告
LOJ #2497 Banany 题解
题目描述
给你一棵 \(n\) 个节点的树,每个节点有权值 \(w_i\),每一条边有权值 \(v_i\)。定义从 \(s\) 到 \(t\) 的路径的权值为 \(w_t-\sum_{e \in path(s,t)} v_e\)。一共有 \(q\) 次询问,每次询问为修改一个点权或修改边权后,从当前节点出发,能到达的权值最大的点。若有多个权值相同的点选择编号最小的点作为终点,注意,询问后一定需要挪动到其他点,不可以呆着不走。初始时,你在 \(1\) 号节点。
题目分析
先考虑单次询问,不修改的情况。
因为贡献式与根无关,所以不妨设根为 \(1\) 。
考虑将 \(\sum _{e \in path(s,t)}v_e\) 拆成 \(dis(s)+dis(t)-2 \times dis(lca)\) 其中 \(lca\) 表示 \(s\) 和 \(t\) 的最近公共祖先,\(dis(x)\) 为从根到 \(x\) 的路径的长度。
那么我们可以枚举 \(t\),利用欧拉序求最近公共祖先 以及预处理 \(dis(x)\) 可以做到单次 \(O(1)\) 的更新答案。
总时间复杂度 \(O(n)\)。
再考虑不带修但是多次询问不同起点的简化问题。
因为我们是从 \(s\) 出发去寻找 \(t\) 的,所以 \(dis(s)\) 是固定的。
这启发我们在 \(lca\) 处统计点对 \(s\) 到 \(t\) 的贡献。
写出只与 \(lca\) 和 \(t\) 有关的贡献式,也就是 \(2 \times dis(lca)+w_t-dis(t)\) 。因为我们在枚举 \(lca\) 统计答案,所以 \(dis(lca)\) 也是定值,即能作出贡献的就是 \(lca\) 子树中的点。
但是这样统计可能会重复。设 \(p\) 为 \(lca\) 的祖先。则我们在 \(p\) 处同样会统计到 \(t\) 的贡献,但是 \(s\) 与 \(t\) 的最近公共祖先不是 \(p\)。
虽然如此,因为边权没有负边权,所以 \(dis(p)<dis(lca)\)。这就意味着,虽然我们重复统计 \(t\),但是在 \(p\) 统计一定比在 \(lca\) 统计劣,所以这样统计是正确的。
同时,这种统计方法也说明在不同的 \(s\) 的情况下,如果我们没有修改边权与点权,那么我们会选择同样的 \(t\) 作为终点,除了 \(t\) 本身。
考虑枚举 \(lca\),根据上述证明,我们只需要找到在 \(lca\) 子树中,拥有最大 \(w_t-dis(t)\) 的 \(t\) 与次大的 \(t'\) 即可。(需要 \(t'\) 是因为不能停留在同一个点,所以需要次大值来更新)。这可以使用 \(O(n)\) 的深搜来实现。
时间复杂度 \(O(n)\)。
现在我们解决了不带修的多次询问起点的简化问题。
考虑带修改的怎么做。
首先总结下这道题的性质:
- 暴力更新答案是朴素的,即我们可以 \(O(1)\) 计算 \(t\) 对 \(s\) 的贡献。
- 在不带修的情况下对于不同的 \(s\) 我们需要维护的东西是相同的。
这两点,启发我们使用对询问分块这种做法。
具体的,我们每 \(\sqrt n\) 个询问分段,暴力重构一次原树。其中,重构定义为将简化问题中需要维护的 \(t\) 与 \(t'\) 通过 \(O(n)\) 暴力维护出来,但我们并不维护在这 \(\sqrt n\) 中被修改点权的点所作出的贡献。并且,我们将修改过边权的边标记出来,这样原树最多被分成 \(\sqrt n\) 个子树。
考虑将问题转化为一下几个部分计算:
- \(lca\) 与 \(t\) 在同一个子树内,且 \(t\) 并未修改过。
- \(lca\) 与 \(t\) 不在同一子树内,且 \(t\) 并未修改过。
- \(t\) 的点权在这 \(\sqrt n\) 个操作中被修改过。
容易发现,这三种情况包含了所有可能的贡献情况。
第一部分 \(lca\) 与 \(t\) 在同一子树:
写出贡献式子 \(2 \times dis(lca)+w_t-dis(t)\)。因为我们钦定了 \(t\) 未修改点权,所以可能对这个贡献产生影响的操作就是修改边权操作。因为我们把更改过边权的边标记出来了,也就是说,对于每个子树中的所有 \(dis(x)\),他们的 \(\Delta dis\) 是固定的。所以我们只需要对每个子树维护 \(\Delta dis\),具体来说,我们建出这些子树的根所形成的虚树 ,在修改某条边边权的时候,我们直接遍历这棵虚树的子树中的所有点就好了。但是我们并不需要 \(O(siz)\) 的单调栈建立的办法,直接暴力建树就好了。反正 \(O(n \sqrt n)\) 和 \(O(n)\)差别不大 。
时间复杂度分为两部分:
-
用每个子树的 \(t\) 和 \(t'\) 更新答案,一共 \(\sqrt n\) 个子树,需要更新 \(\sqrt n\) 个答案,所以对于划分好的每段询问的时间复杂度为 \(O(n)\),总共划分出了 \(\sqrt n\) 段,所以总时间复杂度 \(O(n \sqrt n)\)。
-
维护每棵子树的 \(\Delta dis\),修改共有 \(\sqrt n\) 个,虚树的大小为 \(\sqrt n\),每段询问的时间复杂度为 \(O(n)\),\(\sqrt n\) 段的复杂度 \(O(n \sqrt n)\)。
第二部分 \(lca\) 与 \(t\) 不在同一子树:
因为 \(lca\) 和 \(t\) 不在同一子树,所以 \(t\) 所在子树的所有点都在 \(lca\) 的子树中。根据上面的子问题中的结论,\(lca\) 只会选择 \(t\) 所在子树中 \(w_t-dis(t)\) 最大的 \(t\) 与次大的 \(t'\)。对于 \(t\) 所在子树中的点来说 \(\Delta dis\) 是固定的,那么 \(w_t-dis(t)\) 的最大的 \(t\),与次大的 \(t'\) 就是我们最开始重构所维护出的 \(t\) 与 \(t'\) 。
同第一部分的时间复杂度分析,总时间复杂度为 \(O(n\sqrt n)\)。
第三部分,修改过的 \(t\) 对答案做出贡献:
因为修改过的 \(t\) 只会有 \(\sqrt n\) 个,所以我们可以用最开始提到的 \(O(1)\) 更新点对的方法,更新答案。
时间复杂度同前面两部分一样分析,为 \(O(n \sqrt n)\)。
于是我们就轻松把这道题做完了。
代码实现
代码大致可分为:
-
prework和ST_table,用来 \(O(1)\) 求解最近公共祖先。 -
upd用于将 \(y\) 的 \(t\) 与 \(t'\) 给 \(x\) 的 \(t\) 与 \(t'\) 做贡献。 -
update用于更新每个子树的 \(\Delta dis\)。 -
Dis用于计算 \(x\) 真正的 \(dis(x)\)。 -
Upans用于更新答案。 -
prebuild用于计算同一子树中的子树的 \(t\) 与 \(t'\)。(有点绕,第一个子树是指把修改过边权的边标记后,形成的 \(\sqrt n\) 个子树,第二个子树,就是子树。) -
rebuild重构函数,用于维护 \(t\) 与 \(t'\) 的后缀最优解用于快速更新统计答案的第一部分。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
template <typename T> inline void read(T &x)
{
x=0;T f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-')f=-1;
for(;isdigit(c);c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x*=f;
}
template <typename T,typename ...Args>void read(T &x,Args&...args){read(x),read(args...);}
template <typename T> void print(T x)
{
if(x<0) x=-x,putchar('-');
if(x>9) print(x/10);
putchar(x%10+48);
}
template <typename T> void print(T x,char c){print(x); putchar(c);}
template<typename T>inline void output(T x){print(x,' ');}
template<typename T,typename ...Arg>inline void output(T x,Arg ...arg){output(x);output(arg...);}
const int N=200007,M=400;
int n,q,cnt,h[N],euler[N],idx,pos[N],ans,nxt,pa[N],res1[N],res2[N],vis[N],vise[N],top[N];
ll val[N],vale[N],temp,dis[N],now1[N],now2[N],tag[N];
vector<int>chg,chge,edg[N];
struct edge{int to,nxt;}mp[N<<1];
struct ST_table
{
int f[20][N],lg[N];
void prework()
{
for(int i=2;i<N;i++) lg[i]=lg[i>>1]+1;
for(int i=1;i<n<<1;i++) f[0][i]=euler[i];
for(int i=1;i<20;i++)
for(int j=1;j+(1<<i)<=n<<1;j++)
{
if(pos[f[i-1][j]]<pos[f[i-1][j+(1<<(i-1))]]) f[i][j]=f[i-1][j];
else f[i][j]=f[i-1][j+(1<<(i-1))];
}
}
int ask(int l,int r)
{
int len=lg[r-l+1];
if(pos[f[len][l]]<pos[f[len][r-(1<<len)+1]]) return f[len][l];
else return f[len][r-(1<<len)+1];
}
int LCA(int x,int y){return ask(min(pos[x],pos[y]),max(pos[x],pos[y]));}
}__st;
struct node{int opt,pos; ll val;}ask[N];
void add(int x,int y)
{
cnt++;
mp[cnt].nxt=h[x];
mp[cnt].to=y;
h[x]=cnt;
}
void prework(int x,int fa)
{
euler[++idx]=x; pos[x]=idx; pa[x]=fa;
for(int i=h[x];i;i=mp[i].nxt)
{
int y=mp[i].to;
if(y==fa) continue;
vale[y]=dis[(i+1)>>1];
prework(y,x); euler[++idx]=x;
}
}
void upd(int x,int y)
{
if(now1[x]>now1[y])
{
ll mx=-0x3f3f3f3f3f3f3f3f; int pos=0;
if(now2[x]>mx) mx=now2[x],pos=res2[x];
else if(now2[x]==mx) pos=min(pos,res2[x]);
if(res1[y]!=res1[x])
{
if(now1[y]>mx) mx=now1[y],pos=res1[y];
else if(now1[y]==mx) pos=min(pos,res1[y]);
}
if(res2[y]!=res1[x])
{
if(now2[y]>mx) mx=now2[y],pos=res2[y];
else if(now2[y]==mx) pos=min(pos,res2[y]);
}
res2[x]=pos; now2[x]=mx;
}
else
{
ll mx=-0x3f3f3f3f3f3f3f3f; int pos=0;
if(now2[y]>mx) mx=now2[y],pos=res2[y];
else if(now2[y]==mx) pos=min(pos,res2[y]);
if(res1[x]!=res1[y])
{
if(now1[x]>mx) mx=now1[x],pos=res1[x];
else if(now1[x]==mx) pos=min(pos,res1[x]);
}
if(res2[x]!=res1[y])
{
if(now2[x]>mx) mx=now2[x],pos=res2[x];
else if(now2[x]==mx) pos=min(pos,res2[x]);
}
res1[x]=res1[y]; now1[x]=now1[y];
res2[x]=pos; now2[x]=mx;
}
}
void update(int x,ll del)
{
tag[x]+=del;
for(auto y:edg[x])
update(y,del);
}
ll Dis(int x){return dis[x]+tag[top[x]];}
bool Updans(int x)
{
if(!x||x==ans) return false;
int lca=__st.LCA(ans,x); ll now=2*Dis(lca)+val[x]-Dis(x);
if(now>temp) temp=now,nxt=x;
else if(now==temp) nxt=min(nxt,x);
return true;
}
void prebuild(int x,int fa)
{
now1[x]=now2[x]=-0x3f3f3f3f3f3f3f3f; res1[x]=res2[x]=0;
if(!vis[x]) res1[x]=x,now1[x]=val[x]-dis[x];
for(int i=h[x];i;i=mp[i].nxt)
{
int y=mp[i].to;
if(y==fa) continue;
dis[y]=dis[x]+vale[y];
prebuild(y,x);
if(!vise[y]) upd(x,y);
}
}
void rebuild(int x,int fa,int ntop)
{
top[x]=ntop;
for(int i=h[x];i;i=mp[i].nxt)
{
int y=mp[i].to;
if(y==fa) continue;
if(!vise[y]) upd(y,x);
if(vise[y]) rebuild(y,x,y),edg[ntop].push_back(y);
else rebuild(y,x,ntop);
}
}
int main()
{
read(n,q); ans=1;
for(int i=1;i<=n;i++) read(val[i]);
for(int i=1,x,y;i<n;i++)
read(x,y,dis[i]),add(x,y),add(y,x);
prework(1,0); __st.prework();
for(int i=1,x,y;i<=q;i++)
{
read(ask[i].opt);
if(ask[i].opt==1) read(ask[i].pos,ask[i].val);
else
{
read(x,y);
if(pos[x]<pos[y]) swap(x,y);
ask[i].pos=x; read(ask[i].val);
}
}
for(int i=1;i<=q;i+=M)
{
chg.resize(0); chge.resize(0); dis[1]=0;
for(int j=i;j<=q&&j-i<M;j++)
{
if(ask[j].opt==1) vis[ask[j].pos]=1,chg.push_back(ask[j].pos);
else vise[ask[j].pos]=1,chge.push_back(ask[j].pos);
}
prebuild(1,0); chge.push_back(1);
for(int j=1;j<=n;j++)
{
if(res1[j]) now1[j]+=dis[j]*2;
if(res2[j]) now2[j]+=dis[j]*2;
}
rebuild(1,0,1);
for(int j=i,now;j<=q&&j-i<M;j++)
{
temp=-0x3f3f3f3f3f3f3f3f;
if(ask[j].opt==1) val[ask[j].pos]=ask[j].val;
else update(ask[j].pos,ask[j].val-vale[ask[j].pos]),vale[ask[j].pos]=ask[j].val;
for(auto pot:chg) Updans(pot);
for(auto pot:chge) if(!Updans(res1[pot])) Updans(res2[pot]);
now=ans; while(now){if(!Updans(res1[now])) Updans(res2[now]); now=pa[top[now]];}
ans=nxt; print(ans,' ');
}
for(auto pot:chge) tag[pot]=0,edg[pot].resize(0);
for(int j=i;j<=q&&j-i<M;j++)
{
if(ask[j].opt==1) vis[ask[j].pos]=0;
else vise[ask[j].pos]=0;
}
}
return 0;
}

浙公网安备 33010602011771号