洛谷 P1505 [国家集训队]旅游

链接:P1505

题意:

给定一棵树,边带权值,五种操作:

  • 修改一条边的权值
  • 将两节点之间的所有边权值变为相反数
  • 询问两节点间边权和
  • 询问两点间最大权值
  • 询问两点间最小权值

分析:

所有操作都是对树上路径的询问和修改,说明这是道树链剖分模板题,思想简单,码量较大,也有一些踩坑的地方,适合初学树剖的来练手。


算法:

先解释下这几个:
ttol[N],ltot[N],wtop[N]; 意思分别是treetoline,linetotree,weighttopoint前两个即树剖中树与线段树的对应关系,后一个是题目要求的第i条输入的边对应的树上节点的编号。

化边为点,即将边权值转移到儿子节点上,只需在树剖dfs1中加入如下代码:
w[v]=e[i].w;

然后正常树链剖分,对边权值建线段树,此时会多出不存在的一条根节点的边权值,对后续没有影响。求和 sum ,最大值 maxn ,最小值minnpushup维护,区间取负维护一个懒标记,单点修改直接暴力下传。

取负时 sum-1,最大值和最小值发现只需要交换再分别取负即可。下传标记的操作如下:

inline void f(int p,int l,int r,int k){
	mul[p]*=k;
	sum[p]*=k;
	swap(minn[p],maxn[p]);
	minn[p]*=k;
	maxn[p]*=k;
}

以上线段树。

对于树链剖分,发现化边为点后不能直接对这两点之间的点进行操作,因为他们的 lca 的值并不在他们的路径上,但由于跳链到最后xy在一条重链,所以他们在序列中连续,可以直接将lca在序列中的位置++,来避开lca

思想其实很简单,就是码量较大。

如果难以实现,可以参考我的代码,不过有亿点长。


代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long

inline int read(){
	int p=0,f=1;
	char c=getchar();
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
	while(c>='0'&&c<='9'){p=(p<<1)+(p<<3)+(c-48);c=getchar();}
	return p*f;
}

const int N=200005;

struct edge{
	int o;
	int v;
	int w;
	int next;
}e[2*N];
int head[N],en;
inline void insert(int u,int v,int w,int o){
	e[++en].v=v;
	e[en].o=o;
	e[en].w=w;
	e[en].next=head[u];
	head[u]=en;
}

int w[N],deep[N],fa[N],size[N],hson[N],top[N];
int ttol[N],ltot[N],wtop[N];
int tot;

//----------------------------ST
int sum[N<<2],mul[N<<2],minn[N<<2],maxn[N<<2];
inline void f(int p,int l,int r,int k){
	mul[p]*=k;
	sum[p]*=k;
	swap(minn[p],maxn[p]);
	minn[p]*=k;
	maxn[p]*=k;
}
inline void pushdown(int x,int l,int r){
	if(mul[x]!=1){
		int mid=(l+r)>>1;
		f(x<<1,l,mid,mul[x]);
		f(x<<1|1,mid+1,r,mul[x]);
		mul[x]=1;		
	}
}
inline void pushup(int x,int l,int r){
	if(l==r)return;
	sum[x]=sum[x<<1]+sum[x<<1|1];
	maxn[x]=max(maxn[x<<1],maxn[x<<1|1]);
	minn[x]=min(minn[x<<1],minn[x<<1|1]);
}
inline void built(int l,int r,int p){
	mul[p]=1;
	if(l==r){
		maxn[p]=minn[p]=sum[p]=w[ltot[l]];
		return;
	}
	int mid=(l+r)>>1;
	built(l,mid,p<<1);
	built(mid+1,r,p<<1|1);
	pushup(p,l,r);
}
inline void assign(int l,int r,int p,int x,int t){//单点修改 
	pushdown(p,l,r);
	if(l==r){
		mul[p]=1;
		maxn[p]=minn[p]=sum[p]=t;
		return ;
	}
	int mid=(l+r)>>1;
	if(x>mid)assign(mid+1,r,p<<1|1,x,t);
	else assign(l,mid,p<<1,x,t);
	pushup(p,l,r);
}
inline void changeit(int cl,int cr,int l,int r,int p,int t){//区间乘 
	if(l>=cl&&r<=cr){
		f(p,l,r,t);
		return ;
	}
	pushdown(p,l,r);
	int mid=(l+r)>>1;
	if(cr>mid)changeit(cl,cr,mid+1,r,p<<1|1,t);
	if(cl<=mid)changeit(cl,cr,l,mid,p<<1,t);
	pushup(p,l,r);
	return ;
}
inline int queryit_sum(int ql,int qr,int l,int r,int p){//区间求和 
	if(l>=ql&&r<=qr)
		return sum[p];
	pushdown(p,l,r);
	int mid=(l+r)>>1,res=0;
	if(qr>mid)res+=queryit_sum(ql,qr,mid+1,r,p<<1|1);
	if(ql<=mid)res+=queryit_sum(ql,qr,l,mid,p<<1);
	return res;
}
inline int queryit_max(int ql,int qr,int l,int r,int p){//区间最大值 
	if(l>=ql&&r<=qr)
		return maxn[p];
	pushdown(p,l,r);
	int mid=(l+r)>>1,res=-0x7fffffff;
	if(qr>mid)res=max(res,queryit_max(ql,qr,mid+1,r,p<<1|1));
	if(ql<=mid)res=max(res,queryit_max(ql,qr,l,mid,p<<1));
	return res;
}
inline int queryit_min(int ql,int qr,int l,int r,int p){//区间最小值 
	if(l>=ql&&r<=qr)
		return minn[p];
	pushdown(p,l,r);
	int mid=(l+r)>>1,res=0x7fffffff;
	if(qr>mid)res=min(res,queryit_min(ql,qr,mid+1,r,p<<1|1));
	if(ql<=mid)res=min(res,queryit_min(ql,qr,l,mid,p<<1));
	return res;
}
//---------------------------------------------------ST
//---------------------------------------------------树剖
inline void dfs1(int s){
	size[s]=1;
	hson[s]=0;
	for(int i=head[s];i;i=e[i].next){
		if(e[i].v==fa[s][0])continue;
		int v=e[i].v;
		w[v]=e[i].w;//化边为点
		wtop[e[i].o]=v;//e[i].o是边的输入次序
		deep[v]=deep[s]+1;
		fa[v]=s;
		dfs1(v);
		size[s]+=size[v];
		if(size[v]>size[hson[s]])hson[s]=v;
	}
	return ;
}
inline void dfs2(int s,int tp){
	top[s]=tp;
	ttol[s]=++tot;
	ltot[tot]=s;
	if(hson[s])dfs2(hson[s],tp);
	for(int i=head[s];i;i=e[i].next)
		if(e[i].v!=fa[s]&&e[i].v!=hson[s])		
			dfs2(e[i].v,e[i].v);	
	return ;
}
//以下四个函数为树剖模板,在树上跳链并修改/查询 
inline void change(int x,int y,int z=-1){
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]])
			swap(x,y);
		changeit(ttol[top[x]],ttol[x],1,tot,1,z);
		x=fa[top[x]];
	}
	if(deep[x]>deep[y])
		swap(x,y);
	changeit(ttol[x]+1,ttol[y],1,tot,1,z);
}
inline int query_sum(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]])
			swap(x,y);
		ans+=queryit_sum(ttol[top[x]],ttol[x],1,tot,1);
		x=fa[top[x]];
	}
	if(deep[x]>deep[y])
		swap(x,y);
	ans+=queryit_sum(ttol[x]+1,ttol[y],1,tot,1);
	return ans;
}
inline int query_max(int x,int y){
	int ans=-0x7fffffff;
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]])
			swap(x,y);
		ans=max(ans,queryit_max(ttol[top[x]],ttol[x],1,tot,1));
		x=fa[top[x]];
	}
	if(deep[x]>deep[y])
		swap(x,y);	
	ans=max(ans,queryit_max(ttol[x]+1,ttol[y],1,tot,1));
	return ans;
}
inline int query_min(int x,int y){
	int ans=0x7fffffff;
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]])
			swap(x,y);
		ans=min(ans,queryit_min(ttol[top[x]],ttol[x],1,tot,1));
		x=fa[top[x]];
	}
	if(deep[x]>deep[y])
		swap(x,y);
	ans=min(ans,queryit_min(ttol[x]+1,ttol[y],1,tot,1));
	return ans;
}
//-------------------------------------------------树剖 
int n,m;
signed main(){
	n=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read(),w=read();
		insert(u+1,v+1,w,i);//为了方便处理将点的编号全部加1,即从1到n 
		insert(v+1,u+1,w,i);
	}
	dfs1(1);
	dfs2(1,1);
	built(1,tot,1);
	m=read();
	for(int i=1;i<=m;i++){
		char opt[10];
		scanf("%s",opt);
		int u=read(),v=read();
		u++;v++;
		if(opt[0]=='C')assign(1,tot,1,ttol[wtop[u-1]],v-1);//不是编号所以不能++;wtop从边次序到点位置,ttol从树上跳到序列上 
		if(opt[0]=='N')change(u,v);
		if(opt[0]=='S')printf("%lld\n",query_sum(u,v));
		if(opt[0]=='M'&&opt[1]=='A')printf("%lld\n",query_max(u,v));
		if(opt[0]=='M'&&opt[1]=='I')printf("%lld\n",query_min(u,v));
	}
    return 0;
}

题外话:

一开始想用倍增求LCA排除掉它的影响,是我太菜了。
怪不得我写得又长又不好调(((

posted @ 2021-05-19 17:04  llmmkk  阅读(68)  评论(0编辑  收藏  举报