洛谷 P1505 [国家集训队]旅游
链接:P1505
题意:
给定一棵树,边带权值,五种操作:
- 修改一条边的权值
- 将两节点之间的所有边权值变为相反数
- 询问两节点间边权和
- 询问两点间最大权值
- 询问两点间最小权值
分析:
所有操作都是对树上路径的询问和修改,说明这是道树链剖分模板题,思想简单,码量较大,也有一些踩坑的地方,适合初学树剖的来练手。
算法:
先解释下这几个:
ttol[N],ltot[N],wtop[N];
意思分别是treetoline,linetotree,weighttopoint
前两个即树剖中树与线段树的对应关系,后一个是题目要求的第i条输入的边对应的树上节点的编号。
化边为点,即将边权值转移到儿子节点上,只需在树剖dfs1
中加入如下代码:
w[v]=e[i].w;
然后正常树链剖分,对边权值建线段树,此时会多出不存在的一条根节点的边权值,对后续没有影响。求和 sum
,最大值 maxn
,最小值minn
用pushup
维护,区间取负维护一个懒标记,单点修改直接暴力下传。
取负时 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
的值并不在他们的路径上,但由于跳链到最后x
和y
在一条重链,所以他们在序列中连续,可以直接将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排除掉它的影响,是我太菜了。
怪不得我写得又长又不好调(((