把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

CF1192B/CEOI2019 动态直径 Dynamic Diameter【欧拉序-线段树】

题目链接

题目解析

唔,首先有一个比较显然的大概是nq级别复杂度的做法,就是暴力修改,然后dfs两次算出直径。

(不过这没有什么用就是了

因为有修改,我们尝试把树下下来,放到序列上,用线段树维护。

树链剖分?

不,太麻烦喏,我们知道欧拉序这个东西是可以办到的,并且我们之前就用它的性质求过lca:两个点对应的欧拉序区间中,深度最小的点就是这两个点的lca

具体而言,设u在欧拉序中第一次出现的位置为lv在欧拉序中第一次出现的位置为r,欧拉序数组为E[],那么lca=E[x],l<=x<=r,dep[E[x]]<=dep[E[l...r]]

如果不知道这个结论可以看看 这个

那么dist(u,v)=dis[u]+dis[v]2×dis[lca]dis[i]指从1i的距离。

根据定义,树的直径是所有点对dist的最大值

如果用线段树维护(相当于是线段树维护与欧拉序数组对应的点的dis,长度为2n1,完成把树变成序列的任务),那对于区间[l,r]的答案长这个样子:

max{dis[i]+dis[j]2×dis[lca(i,j)]},l<=i<=j<=r

我们注意到lca对于i,j来说是一个rmq问题,那......线段树套线段树?

没有必要。

具体到这道题而言,由于边权都是正数,所以lca也是dis最小的那个点,所以可以把答案化为这个样子:

max{dis[i]+dis[j]2×dis[k]},l<=i<=k<=j<=r

因为dis前面是负号,所以让这个值取max的那个位置一定是dis[k]最小的那个点,所以可以不用再维护[i,j]之间的最小值,而是在维护上述答案的时候自然就处理了(我感觉这里网上很多题解都没有说清楚,我看了好久)


接下来考虑怎么维护上述答案,主要是要便于区间合并。

我们可以把答案看成三部分:dis[i],dis[j],2×dis[k]

对于每个区间,维护:

mx=max(dis[i]),l<=i<=r

mn=min(dis[i]),l<=i<=r,(主要是为了2×dis[k],维护max(dis[i])也可

res=max{dis[i]+dis[j]2×dis[k]},l<=i<=k<=j<=r

为了方便左右儿子合并,也就是i,j可以来源于左右不同儿子,合在一起是一个更优的答案,我们还需要维护以下信息:

rmx=max(dis[i]2×dis[j]),i<=j,这个可以看成我们还需要在右边找一个右端点来组成答案。

同理,lmx=max(dis[i]2×dis[j]),i<=j


然后维护线段树:

t.mx=max(lc.mx,rc.mx)

t.mn=min(lc.mn,rc.mn)

t.res=max(max(lc.res,rc.res),max(lc.rmx+rc.mx,rc.lmx+lc.mx))

t.rmx=max(max(lc.rmx,rc.rmx),lc.mx2rc.mn)

t.lmx=max(max(lc.lmx,rc.lmx),rc.mx2lc.mn)

最后查询的时候是查询整棵树的直径,所以不用写query


修改的时候,答案只会对这条边深度较大的点的子树的答案产生影响,所以对子树进行更新即可(子树在欧拉序中是连续一段区间


►Code View

#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 200005
#define M 200005
#define MOD 998244353
#define INF 0x3f3f3f3f
#define LL long long
LL rd()
{
	LL x=0,f=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	return f*x;
}
struct edge{
	int nxt,v;LL w;
}e[M<<1];
int hd[N],cnt=1;
void add(int u,int v,LL w)
{
	e[++cnt].nxt=hd[u];
	e[cnt].v=v;
	e[cnt].w=w;
	hd[u]=cnt;
}

struct node{
	LL res/*直径*/,mx/*dis最大*/,mn/*dis最小*/;
	LL rmx/*dis_i-2dis_j(i<=j)的最大值 差一个右端点*/,lmx/*同理 dis_i-2dis_j(i>=j)的最大值*/;
}tree[N<<2];
LL tag[N<<2];
int n,Q;
LL W;
LL dis[N];
int E[N<<1],tim;//欧拉序 
int d1[N],d2[N];//出去进来的时间 即点i在欧拉序中的最左/最右位置 
void dfs(int u,int fa)
{
	E[++tim]=u;
	d1[u]=tim;
	for(int i=hd[u];i;i=e[i].nxt)
	{
		int v=e[i].v;LL w=e[i].w;
		if(v==fa) continue;
		dis[v]=dis[u]+w;
		dfs(v,u);
		E[++tim]=u;
	}
	d2[u]=tim;
}
node Merge(node x,node y)
{
	node t;
	t.mx=max(x.mx,y.mx);
	t.mn=min(x.mn,y.mn);
	t.rmx=max(max(x.rmx,y.rmx),x.mx-2*y.mn);
	t.lmx=max(max(x.lmx,y.lmx),y.mx-2*x.mn);
	t.res=max(max(x.res,y.res),max(x.mx+y.lmx,y.mx+x.rmx));
	return t;
}
void PushUp(int i)
{
	tree[i]=Merge(tree[i<<1],tree[i<<1|1]);
}
void Build(int i,int l,int r)
{
	if(l==r)
	{
		int u=E[l];
		tree[i].mx=tree[i].mn=dis[u];
		tree[i].rmx=tree[i].lmx=-dis[u];//dis_u-2*dis_u=-dis_u
		tree[i].res=0;//一个点不能构成直径
		tag[i]=0;
		return ;
	}
	int mid=(l+r)>>1;
	Build(i<<1,l,mid);
	Build(i<<1|1,mid+1,r);
	PushUp(i);
	return ;
}
void Modify(int i,LL val)
{
	tree[i].mx+=val,tree[i].mn+=val;
	tree[i].lmx-=val,tree[i].rmx-=val;//delta=(dis_i-val)-2*(dis_j-val)-(dis_i-2*dis_j)
	tag[i]+=val;
}
void PushDown(int i)
{
	if(tag[i])
	{
		Modify(i<<1,tag[i]);
		Modify(i<<1|1,tag[i]);
		tag[i]=0;
	}
}
void Update(int i,int l,int r,int ql,int qr,LL val)
{
	if(ql<=l&&r<=qr)
	{
		Modify(i,val);
		return ;
	}
	PushDown(i);
	int mid=(l+r)>>1;
	if(ql<=mid) Update(i<<1,l,mid,ql,qr,val);
	if(qr>mid) Update(i<<1|1,mid+1,r,ql,qr,val);
	PushUp(i);
}
int main()
{
	n=rd(),Q=rd(),W=rd();
	for(int i=1;i<=n-1;i++)
	{
		int u=rd(),v=rd();LL w=rd();
		add(u,v,w);
		add(v,u,w);
	}
	dfs(1,0);
	Build(1,1,tim);
	LL ans=0;
	while(Q--)
	{
		int id=rd(),u;LL d=rd();
		id=(id+ans)%(n-1)+1;
		d=(d+ans)%W;
		if(dis[e[id<<1].v]<dis[e[id<<1|1].v]) id=(id<<1|1),u=e[id].v;
		else id=(id<<1),u=e[id].v;
		Update(1,1,tim,d1[u],d2[u],d-e[id].w);
		e[id].w=e[id^1].w=d;
		ans=tree[1].res;
		printf("%lld\n",ans);
	}
	return 0;
}
/*
修改是给的边的编号 这个时候还是写前向星好一点吧
(怎么感觉vector一无是处了qwq 
*/
posted @   Starlight_Glimmer  阅读(327)  评论(1编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示