P4114 Qtree1 题解

文章介绍了如何使用树链剖分的方法处理一棵有n个节点和n-1条边的树,通过边权转化和最近公共祖先策略,实现CHANGE操作改变边权和QUERY操作查询路径上边权最小值的问题。代码展示了具体的实现步骤和数据结构的运用。

双倍经验:SP375

题目大意

给定一颗 n n n 个节点的树,有 n − 1 n - 1 n1 条边,给定每条边的两个端点和边权。

这里有两种操作,询问以 DONE 结束。

  • 第一种为 CHANGE 操作,输入格式为 CHANGE i t,表示将第 i i i 条边的边权改为 t t t

  • 第二种为 QUERY 操作,输入格式为 QUERY a b,表示查询从节点 a a a 到节点 b b b 的路径上边权最小值。

注意:本题多组数据,请在每轮循环结束后清空所有边。

解法

考虑树链剖分做法。

看到询问边权,我们考虑将边权转化为点权,即把一条链接这父节点和子节点的边的边权转化为子节点的点权。

对于两个节点,它们之间的路径一定会经过两个点的最近公共祖先,如下图。节点 1 1 1 和节点 3 3 3 之间的路径经过了节点 2 2 2,而我们将 4 4 4 2 2 2 的边权转化为了点 2 2 2 的点权,我们要避开他们的最近公共祖先,这个只要在查询时查询从 i d a + 1 id_a +1 ida+1 i d b id_b idb 之间的点权最大值即可。代码与树链剖分模板题很相似,大部分可以直接粘过来用。

代码

#include<bits/stdc++.h>
using namespace std;
#define ls l,mid,rt*2
#define rs mid+1,r,rt*2+1
#define lc rt*2
#define rc rt*2+1
#define Getchar() p1==p2 and (p2=(p1=Inf)+fread(Inf,1,1<<21,stdin),p1==p2)?EOF:*p1++
#define Putchar(c) p3==p4 and (fwrite(Ouf,1,1<<21,stdout),p3=Ouf),*p3++=c
char Inf[1<<21],Ouf[1<<21],*p1,*p2,*p3=Ouf,*p4=Ouf+(1<<21);
inline void read(int &x,char c=Getchar())
{
	bool f=c!='-';
	x=0;
	while(c<48 or c>57) c=Getchar(),f&=c!='-';
	while(c>=48 and c<=57) x=(x<<3)+(x<<1)+(c^48),c=Getchar();
	x=f?x:-x;
}
inline void write(int x)
{
	if(x<0) Putchar('-'),x=-x;
	if(x>=10) write(x/10),x%=10;
	Putchar(x^48);
}
int t,n,dep[10010],f[10010],siz[10010],hvson[10010],id[10010],belong[10010],start[10010],neww[10010],cnt;
int sum[40010];
pair<int,int> Pair[10010];
char ch;
struct edge
{
	int to,cost;
};
vector<edge> v[100010];
inline void dfs1(int pos,int fa,int depth,int maxi=-0x3f3f3f3f)
{
	dep[pos]=depth,f[pos]=fa,siz[pos]=1;
	for(int i=0;i<v[pos].size();i++)
	{
		if(v[pos][i].to!=fa)
		{
			neww[v[pos][i].to]=v[pos][i].cost,dfs1(v[pos][i].to,pos,depth+1),siz[pos]+=siz[v[pos][i].to];
			if(siz[v[pos][i].to]>maxi) hvson[pos]=v[pos][i].to,maxi=siz[v[pos][i].to];
		}
	}
}
inline void dfs2(int pos,int Start)
{
	id[pos]=++cnt,belong[cnt]=pos,start[pos]=Start;
	if(hvson[pos])
	{
		dfs2(hvson[pos],Start);
		for(int i=0;i<v[pos].size();i++)
			if(v[pos][i].to!=f[pos] && v[pos][i].to!=hvson[pos]) dfs2(v[pos][i].to,v[pos][i].to);
	}
}
inline void pushup(const int &rt)
{
	sum[rt]=max(sum[lc],sum[rc]);
}
inline void build(int l,int r,int rt)
{
	if(l==r)
	{
		sum[rt]=neww[belong[l]];
		return;
	}
	int mid=(l+r)/2;
	build(ls),build(rs),pushup(rt);
}
inline void fix(const int &pos,const int &val,int l,int r,int rt)
{
	if(l==r)
	{
		sum[rt]=val;
		return;
	}
	int mid=(l+r)/2;
	if(pos<=mid) fix(pos,val,ls);
	else fix(pos,val,rs);
	pushup(rt);
}
inline int ask(const int &L,const int &R,int l,int r,int rt)
{
	if(L>R) return 0;
	if(L<=l && r<=R) return sum[rt];
	int mid=(l+r)/2,ret=0;
	if(L<=mid) ret=max(ret,ask(L,R,ls));
	if(mid<R) ret=max(ret,ask(L,R,rs));
	return ret;
}
inline int ask(int x,int y)
{
	if(x==y) return 0;
	int ret=0;
	while(start[x]!=start[y])
	{
		if(dep[start[x]]<dep[start[y]]) swap(x,y);
		ret=max(ret,ask(id[start[x]],id[x],1,n,1)),x=f[start[x]];
	}
	if(dep[x]>dep[y]) swap(x,y);
	ret=max(ret,ask(id[x]+1,id[y],1,n,1));
	return ret;
}
int main()
{
	read(t);
	while(t--)
	{
		read(n),cnt=0,memset(hvson,0,sizeof(hvson));
		for(int i=1,x,y,z;i<n;i++)
			read(x),read(y),read(z),v[x].push_back((edge){y,z}),v[y].push_back((edge){x,z}),Pair[i]=make_pair(x,y);
		dfs1(1,0,1),dfs2(1,1),build(1,n,1);
		for(int x,y;;)
		{
			ch=Getchar();
			while(ch<'A' || ch>'Z') ch=Getchar();
			if(ch=='D') break;
			read(x),read(y);
			if(ch=='C')
			{
				if(f[Pair[x].second]==Pair[x].first) swap(Pair[x].first,Pair[x].second);
				fix(id[Pair[x].first],y,1,n,1);
			}
			else write(ask(x,y)),Putchar('\n');
		}
		for(int i=1;i<=n;i++) v[i].clear();
	}
	fwrite(Ouf,1,p3-Ouf,stdout),fflush(stdout);
	return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值