P1505 [国家集训队]旅游
人生第二次单切紫题,值得纪念!!!
[国家集训队]旅游
题目背景
Ray 乐忠于旅游,这次他来到了 T 城。T 城是一个水上城市,一共有 n 个景点,有些景点之间会用一座桥连接。为了方便游客到达每个景点但又为了节约成本,T 城的任意两个景点之间有且只有一条路径。换句话说, T 城中只有 n−1 座桥。
Ray 发现,有些桥上可以看到美丽的景色,让人心情愉悦,但有些桥狭窄泥泞,令人烦躁。于是,他给每座桥定义一个愉悦度 w,也就是说,Ray 经过这座桥会增加 w 的愉悦度,这或许是正的也可能是负的。有时,Ray 看待同一座桥的心情也会发生改变。
现在,Ray 想让你帮他计算从 u 景点到 v 景点能获得的总愉悦度。有时,他还想知道某段路上最美丽的桥所提供的最大愉悦度,或是某段路上最糟糕的一座桥提供的最低愉悦度。
题目描述
给定一棵 n 个节点的树,边带权,编号 0∼n−1,需要支持五种操作:
C i w
将输入的第 i 条边权值改为 wN u v
将 u,v 节点之间的边权都变为相反数SUM u v
询问 u,v 节点之间边权和MAX u v
询问 u,v 节点之间边权最大值MIN u v
询问 u,v 节点之间边权最小值
保证任意时刻所有边的权值都在 [−1000,1000]内。
输入格式
第一行一个正整数 n,表示节点个数。
接下来 n−1 行,每行三个整数 u,v,w,表示u,v之间有一条权值为w 的边,描述这棵树。
然后一行一个正整数 m,表示操作数。
接下来 m行,每行表示一个操作。
输出格式
对于每一个询问操作,输出一行一个整数表示答案。
样例 #1
样例输入 #1
3
0 1 1
1 2 2
8
SUM 0 2
MAX 0 2
N 0 1
SUM 0 2
MIN 0 2
C 1 3
SUM 0 2
MAX 0 2
样例输出 #1
3
2
1
-1
5
3
提示
【数据范围】
对于100%的数据,1≤n,m≤2×105。
浅读一下题,发现有点类似树剖,不过这道题是修改和查询边权。
考虑边权转点权,挑选一个根节点,dfs时将边权下放到深度更大的点上。
然后就是基本的树剖操作了:
//........................................树剖基操 void dfs1(int x,int y) { d[x]=d[y]+1; si[x]=1; for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(v==y) continue; a[v]=w[i],b[(i+1)/2]=v;//特殊处理:b[i]==v表示第i条边的信息处理在结点v上 //由于又是双向存边,所以要用当前边编号加一后除二向下取整 //v为边连接两个结点中深度较大的结点 dfs1(v,x); fa[v]=x; si[x]+=si[v]; if(si[x]>si[son[x]]) son[x]=v; } } void dfs2(int x,int t) { top[x]=t; id[x]=++num; val[num]=a[x]; if(son[x]) dfs2(son[x],t); for(int i=fi[x];i;i=ne[i]) int v=to[i]; if(v==fa[x]||v==son[x]) continue; dfs2(v,v); } } //...........................................over
接下来就是线段树了。
首先介绍懒标记:懒标记维护的是当前结点是否取反,即取相反数。
区间和取相反数加负号就行,最大值和最小值先取相反数,然后互相交换值即可。
void pu(int x)//懒标记下传 { if(la[x]) { tr[x<<1].w=-tr[x<<1].w,tr[x<<1].a=-tr[x<<1].a,tr[x<<1].i=-tr[x<<1].i; tr[x<<1|1].w=-tr[x<<1|1].w,tr[x<<1|1].a=-tr[x<<1|1].a,tr[x<<1|1].i=-tr[x<<1|1].i; //左右子结点均取反 swap(tr[x<<1].a,tr[x<<1].i); swap(tr[x<<1|1].a,tr[x<<1|1].i); //由于取反,故而最大值最小值交换 la[x<<1]^=la[x],la[x<<1|1]^=la[x]; la[x]=0; //处理懒标记 } }
然后基操建树。
对应操作修改和查询。
需要注意的是:由于我们是将边权下放到边连接的两结点中深度较大的结点,故而更新和查询的时候处理的结点不包括边界两边对应两结点的lca。
//.........................................线段树 void bu(int x,int l,int r)//建树 { tr[x].l=l,tr[x].r=r; if(l==r) { tr[x].w=tr[x].a=tr[x].i=val[l]; return; } int mid=(l+r)>>1; bu(x<<1,l,mid),bu(x<<1|1,mid+1,r); tr[x].w=tr[x<<1].w+tr[x<<1|1].w; tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a); tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i); } void u1(int x,int s,int k)//修改对应边权,处理在边权对应的结点上 { pu(x); if(tr[x].l==tr[x].r) { tr[x].w=tr[x].a=tr[x].i=k; return; } int mid=(tr[x].l+tr[x].r)>>1; if(s<=mid) u1(x<<1,s,k); else u1(x<<1|1,s,k); tr[x].w=tr[x<<1].w+tr[x<<1|1].w; tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a); tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i); } void u2(int x,int l,int r)//修改区间,全部取相反数 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) { tr[x].w=-tr[x].w,tr[x].a=-tr[x].a,tr[x].i=-tr[x].i; swap(tr[x].a,tr[x].i); la[x]^=1; return; } int mid=(tr[x].l+tr[x].r)>>1; if(l<=mid) u2(x<<1,l,r); if(r>mid) u2(x<<1|1,l,r); tr[x].w=tr[x<<1].w+tr[x<<1|1].w; tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a); tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i); } void up(int x,int y)//修改树上路径,由于边权对应的是深度大的结点,所以修改不包括两结点的lca { while(top[x]^top[y]) { if(d[top[x]<d[top[y]]]) swap(x,y); u2(1,id[top[x]],id[x]); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); u2(1,id[x]+1,id[y]);//id[x]+1:不修改lca } int q1(int x,int l,int r)//区间和 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) return tr[x].w; int mid=(tr[x].l+tr[x].r)>>1; int s=0; if(l<=mid) s+=q1(x<<1,l,r); if(r>mid) s+=q1(x<<1|1,l,r); return s; } int q2(int x,int l,int r)//区间最大 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) return tr[x].a; int mid=(tr[x].l+tr[x].r)>>1; int s=-INT_MAX; if(l<=mid) s=max(s,q2(x<<1,l,r)); if(r>mid) s=max(s,q2(x<<1|1,l,r)); return s; } int q3(int x,int l,int r)//区间最小 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) return tr[x].i; int mid=(tr[x].l+tr[x].r)>>1; int s=INT_MAX; if(l<=mid) s=min(s,q3(x<<1,l,r)); if(r>mid) s=min(s,q3(x<<1|1,l,r)); return s; } //查询和修改一个道理,不包括lca int qu1(int x,int y)//边权和 { int s=0; while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); s+=q1(1,id[top[x]],id[x]); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); s+=q1(1,id[x]+1,id[y]); return s; } int qu2(int x,int y)//边权最大值 { int s=-INT_MAX; while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); s=max(s,q2(1,id[top[x]],id[x])); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); s=max(s,q2(1,id[x]+1,id[y])); return s; } int qu3(int x,int y)//边权最小值 { int s=INT_MAX; while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); s=min(s,q3(1,id[top[x]],id[x])); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); s=min(s,q3(1,id[x]+1,id[y])); return s; } //..........................................over
Code:
#include<bits/stdc++.h> using namespace std; const int N=4e5+5; int n,m,tot,num; int fa[N],d[N],si[N],son[N],a[N]; int id[N],val[N],top[N],la[N],b[N]; int fi[N],ne[N*2],to[N*2],w[N*2]; struct xiao { int l,r,w,a,i;//w表示区间和,a表示区间最大,i表示区间最小 }tr[N*4]; void add(int x,int y,int z)//手写邻接表 { ne[++tot]=fi[x]; fi[x]=tot; to[tot]=y; w[tot]=z; } void pu(int x)//懒标记下传 { if(la[x]) { tr[x<<1].w=-tr[x<<1].w,tr[x<<1].a=-tr[x<<1].a,tr[x<<1].i=-tr[x<<1].i; tr[x<<1|1].w=-tr[x<<1|1].w,tr[x<<1|1].a=-tr[x<<1|1].a,tr[x<<1|1].i=-tr[x<<1|1].i; //左右子结点均取反 swap(tr[x<<1].a,tr[x<<1].i); swap(tr[x<<1|1].a,tr[x<<1|1].i); //由于取反,故而最大值最小值交换 la[x<<1]^=la[x],la[x<<1|1]^=la[x]; la[x]=0; //处理懒标记 } } //........................................树剖基操 void dfs1(int x,int y) { d[x]=d[y]+1; si[x]=1; for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(v==y) continue; a[v]=w[i],b[(i+1)/2]=v;//特殊处理:b[i]==v表示第i条边的信息处理在结点v上 //由于又是双向存边,所以要用当前边编号加一后除二向下取整 //v为边连接两个结点中深度较大的结点 dfs1(v,x); fa[v]=x; si[x]+=si[v]; if(si[x]>si[son[x]]) son[x]=v; } } void dfs2(int x,int t) { top[x]=t; id[x]=++num; val[num]=a[x]; if(son[x]) dfs2(son[x],t); for(int i=fi[x];i;i=ne[i]) { int v=to[i]; if(v==fa[x]||v==son[x]) continue; dfs2(v,v); } } //...........................................over //.........................................线段树 void bu(int x,int l,int r)//建树 { tr[x].l=l,tr[x].r=r; if(l==r) { tr[x].w=tr[x].a=tr[x].i=val[l]; return; } int mid=(l+r)>>1; bu(x<<1,l,mid),bu(x<<1|1,mid+1,r); tr[x].w=tr[x<<1].w+tr[x<<1|1].w; tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a); tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i); } void u1(int x,int s,int k)//修改对应边权,处理在边权对应的结点上 { pu(x); if(tr[x].l==tr[x].r) { tr[x].w=tr[x].a=tr[x].i=k; return; } int mid=(tr[x].l+tr[x].r)>>1; if(s<=mid) u1(x<<1,s,k); else u1(x<<1|1,s,k); tr[x].w=tr[x<<1].w+tr[x<<1|1].w; tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a); tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i); } void u2(int x,int l,int r)//修改区间,全部取相反数 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) { tr[x].w=-tr[x].w,tr[x].a=-tr[x].a,tr[x].i=-tr[x].i; swap(tr[x].a,tr[x].i); la[x]^=1; return; } int mid=(tr[x].l+tr[x].r)>>1; if(l<=mid) u2(x<<1,l,r); if(r>mid) u2(x<<1|1,l,r); tr[x].w=tr[x<<1].w+tr[x<<1|1].w; tr[x].a=max(tr[x<<1].a,tr[x<<1|1].a); tr[x].i=min(tr[x<<1].i,tr[x<<1|1].i); } void up(int x,int y)//修改树上路径,由于边权对应的是深度大的结点,所以修改不包括两结点的lca { while(top[x]^top[y]) { if(d[top[x]<d[top[y]]]) swap(x,y); u2(1,id[top[x]],id[x]); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); u2(1,id[x]+1,id[y]);//id[x]+1:不修改lca } int q1(int x,int l,int r)//区间和 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) return tr[x].w; int mid=(tr[x].l+tr[x].r)>>1; int s=0; if(l<=mid) s+=q1(x<<1,l,r); if(r>mid) s+=q1(x<<1|1,l,r); return s; } int q2(int x,int l,int r)//区间最大 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) return tr[x].a; int mid=(tr[x].l+tr[x].r)>>1; int s=-INT_MAX; if(l<=mid) s=max(s,q2(x<<1,l,r)); if(r>mid) s=max(s,q2(x<<1|1,l,r)); return s; } int q3(int x,int l,int r)//区间最小 { pu(x); if(l<=tr[x].l&&tr[x].r<=r) return tr[x].i; int mid=(tr[x].l+tr[x].r)>>1; int s=INT_MAX; if(l<=mid) s=min(s,q3(x<<1,l,r)); if(r>mid) s=min(s,q3(x<<1|1,l,r)); return s; } //查询和修改一个道理,不包括lca int qu1(int x,int y)//边权和 { int s=0; while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); s+=q1(1,id[top[x]],id[x]); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); s+=q1(1,id[x]+1,id[y]); return s; } int qu2(int x,int y)//边权最大值 { int s=-INT_MAX; while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); s=max(s,q2(1,id[top[x]],id[x])); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); s=max(s,q2(1,id[x]+1,id[y])); return s; } int qu3(int x,int y)//边权最小值 { int s=INT_MAX; while(top[x]^top[y]) { if(d[top[x]]<d[top[y]]) swap(x,y); s=min(s,q3(1,id[top[x]],id[x])); x=fa[top[x]]; } if(d[x]>d[y]) swap(x,y); s=min(s,q3(1,id[x]+1,id[y])); return s; } //..........................................over int main() { cin>>n; for(int i=1;i<n;i++) { int x,y,z; cin>>x>>y>>z; add(x,y,z),add(y,x,z); } dfs1(0,-1); dfs2(0,0); bu(1,1,n); cin>>m; while(m--) { char c[3]; int x,y; cin>>c; cin>>x>>y; if(c[0]=='C') u1(1,id[b[x]],y); if(c[0]=='N') up(x,y); if(c[0]=='S') cout<<qu1(x,y)<<'\n'; if(c[0]=='M') { if(c[1]=='A') cout<<qu2(x,y)<<'\n'; else cout<<qu3(x,y)<<'\n'; } } }
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现