题目描述
洛谷
给定一棵 \(n\) 个节点的树,边带权,编号 \(0∼n−1\),需要支持五种操作:
- C i w 将输入的第 \(i\) 条边权值改为 \(w\)
- N u v 将 \(u,v\) 节点之间的边权都变为相反数
- SUM u v 询问 \(u,v\) 节点之间边权和
- MAX u v 询问 \(u,v\) 节点之间边权最大值
- MIN u v 询问 \(u,v\) 节点之间边权最小值
数据范围: 对于 100% 的数据,\(1≤n,m≤2×10^5\)
solution
一道很裸但是很毒瘤的树剖题,下放标记着实令人恶心(我在这里疯狂WA) 不说了,讲正解。
首先,抛开取反操作,剩下的操作都很正常,区间和,区间最大值,区间最小值,单点修改。 我们直接上线段树套树剖的板子就可以。
接下来我们考虑取反操作,对于线段树上维护的区间和,区间最大值,区间最小值。
取反时,区间和直接变为相反数,区间最大值和最小值交换一下,在乘以-1就可以了。
代码
void cover(int o)
{
tag(o) *= -1;
sum(o) = -sum(o);
swap(minn(o),maxn(o));
minn(o) = -minn(o);
maxn(o) = -maxn(o);
}
我们还要考虑打标记的问题,1表示没有取反,-1表示取反。
下放时子节点的标记要乘以-1,而不是变为-1
代码
void cover(int o)
{
tag(o) *= -1;
sum(o) = -sum(o);
swap(minn(o),maxn(o));
minn(o) = -minn(o);
maxn(o) = -maxn(o);
}
void down(int o)
{
if(tag(o) == -1)//要取反
{
cover(o<<1); cover(o<<1|1);
tag(o) = 1;
}
}
几个要注意的问题
1.开始时所有的标记全为1,而不是0
2.寻找第x条边时,应判断那个点是孩子。
3.下放标记时,孩子节点的标记要乘以-1,而不是变为-1
4.取最大值时,答案一开始要设成一个负数,不然你就会炸
总代码:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 2e5+10;
string opt;
int n,t,x,y,val,tot,num,u,v;
int size[N],dep[N],son[N],fa[N],top[N],head[N],a[N],w[N],from[N],to[N],dfn[N];
inline int read()
{
int s = 0, w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
return s * w;
}
struct node{int to,net,w;}e[N<<1];
void add_(int x,int y,int w)
{
e[++tot].w = w;
e[tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
void get_tree(int x)
{
dep[x] = dep[fa[x]] + 1; size[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x]) continue;
fa[to] = x; a[to] = e[i].w;
get_tree(to);
size[x] += size[to];
if(size[to] > size[son[x]]) son[x] = to;
}
}
void dfs(int x,int topp)
{
top[x] = topp; dfn[x] = ++num; w[dfn[x]] = a[x];
if(son[x]) dfs(son[x],topp);
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x] || to == son[x]) continue;
dfs(to,to);
}
}
struct Tree
{
struct node{
int lc,rc;
int tag,sum;
int maxn,minn;
}tr[N<<2];
#define l(o) tr[o].lc
#define r(o) tr[o].rc
#define tag(o) tr[o].tag
#define sum(o) tr[o].sum
#define maxn(o) tr[o].maxn
#define minn(o) tr[o].minn
void up(int o)
{
sum(o) = sum(o<<1) + sum(o<<1|1);
maxn(o) = max(maxn(o<<1),maxn(o<<1|1));
minn(o) = min(minn(o<<1),minn(o<<1|1));
}
void cover(int o)
{
tag(o) *= -1;//一定要是乘以-1
sum(o) = -sum(o);
swap(minn(o),maxn(o));
minn(o) = -minn(o);
maxn(o) = -maxn(o);
}
void down(int o)//下放
{
if(tag(o) == -1)
{
cover(o<<1); cover(o<<1|1);
tag(o) = 1;
}
}
void build(int o,int L,int R)
{
l(o) = L, r(o) = R; tag(o) = 1;//标记初始化为1
if(L == R)
{
sum(o) = minn(o) = maxn(o) = w[L];return ;
}
int mid = (L + R)>>1;
build(o<<1,L,mid);
build(o<<1|1,mid+1,R);
up(o);
}
void chenge(int o,int x,int val)//单点修改
{
if(l(o) == r(o))
{
sum(o) = minn(o) = maxn(o) = val; return;
}
down(o);
int mid = (l(o) + r(o))>>1;
if(x <= mid) chenge(o<<1,x,val);
if(x > mid) chenge(o<<1|1,x,val);
up(o);
}
void fan(int o,int L,int R)//取反操作
{
if(L <= l(o) && R >= r(o))
{
cover(o); return;
}
down(o);
int mid = (l(o) + r(o))>>1;
if(L <= mid) fan(o<<1,L,R);
if(R > mid) fan(o<<1|1,L,R);
up(o);
}
int ask_sum(int o,int L,int R)//区间和
{
int ans = 0;
if(L <= l(o) && R >= r(o)) return sum(o);
down(o);
int mid = (l(o) + r(o))>>1;
if(L <= mid) ans += ask_sum(o<<1,L,R);
if(R > mid) ans += ask_sum(o<<1|1,L,R);
return ans;
}
int ask_max(int o,int L,int R)//最大值
{
int ans = -1010;
if(L <= l(o) && R >= r(o)) return maxn(o);
down(o);
int mid = (l(o) + r(o))>>1;
if(L <= mid) ans = max(ans,ask_max(o<<1,L,R));
if(R > mid) ans = max(ans,ask_max(o<<1|1,L,R));
return ans;
}
int ask_min(int o,int L,int R)//维护区间最小值
{
int ans = 1010;
if(L <= l(o) && R >= r(o)) return minn(o);
down(o);
int mid = (l(o) + r(o))>>1;
if(L <= mid) ans = min(ans,ask_min(o<<1,L,R));
if(R > mid) ans = min(ans,ask_min(o<<1|1,L,R));
return ans;
}
}tree;
void qufan(int x,int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
tree.fan(1,dfn[top[x]],dfn[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
tree.fan(1,dfn[x]+1,dfn[y]);
}
int query_sum(int x,int y)
{
int ans = 0;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
ans += tree.ask_sum(1,dfn[top[x]],dfn[x]);
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
ans += tree.ask_sum(1,dfn[x]+1,dfn[y]);
return ans;
}
int query_max(int x,int y)
{
int ans = -1010;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
ans = max(ans,tree.ask_max(1,dfn[top[x]],dfn[x]));
x = fa[x];
}
if(dep[x] > dep[y]) swap(x,y);
ans = max(ans,tree.ask_max(1,dfn[x]+1,dfn[y]));
return ans;
}
int query_min(int x,int y)//跳链修改
{
int ans = 1010;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
ans = min(ans,tree.ask_min(1,dfn[top[x]],dfn[x]));
x = fa[x];
}
if(dep[x] > dep[y]) swap(x,y);
ans = min(ans,tree.ask_min(1,dfn[x]+1,dfn[y]));
return ans;
}
int main()
{
n = read();
for(int i = 1; i <= n-1; i++)
{
u = read() + 1; v = read() + 1; val = read();
add_(u,v,val); add_(v,u,val);
from[i] = u; to[i] = v;//标记每条边的起点和终点
}
get_tree(1); dfs(1,1); tree.build(1,1,n); //初始化
// for(int i = 1; i <= n; i++) cout<<tree.ask_sum(1,i,i)<<endl;
t = read();
while(t--)
{
cin>>opt;
if(opt == "C")
{
x = read(); val = read();
int xx = from[x];
int yy = to[x];
if(fa[xx] == yy) tree.chenge(1,dfn[xx],val);//判断谁是孩子
else tree.chenge(1,dfn[yy],val);
}
if(opt == "N")
{
x = read(); y = read();
qufan(x+1,y+1);//从1开始标号,所以要+1
}
if(opt == "SUM")
{
x = read(); y = read();
printf("%d\n",query_sum(x+1,y+1));
}
if(opt == "MAX")
{
x = read(); y = read();
printf("%d\n",query_max(x+1,y+1));
}
if(opt == "MIN")
{
x = read(); y = read();
printf("%d\n",query_min(x+1,y+1));
}
}
return 0;
}