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)\)

于是我们就轻松把这道题做完了。

代码实现

代码大致可分为:

  • preworkST_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;
}
posted @ 2023-03-10 20:05  slenbol  阅读(47)  评论(0)    收藏  举报