题意
在一个
个结点的树上,每个节点上都有权值。有
次询问。
3种询问:
- 表示 从c1节点到c2节点的路径上所有点权值增加k
- 表示 从c1节点到c2节点的路径上所有点权值减少k
- 表示询问x节点的权值
3 2 5
1 2 3
2 1
2 3
I 1 3 5
Q 2
D 1 2 2
Q 1
Q 3
7
4
8
解法
1. 树链剖分
树的结构是不变的, 可以用树链剖分。
线段树
常规的树链剖分都是用线段树来实现询问更新区间和、单点询问
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define lson(x) ((x)<<1)
#define rson(x) ((x)<<1|1)
#define ms(a,v) memset(a, v, sizeof(a))
const int N = 5e4+5;
int n, m, q;
int dep[N], siz[N], fa[N], id[N], son[N], top[N];
int arr[N];
int val[N];
int num;
struct edge{
int v, next;
}e[N*3];
int head[N];
int cnt;
void add(int u, int v)
{
e[cnt].v = v;
e[cnt].next = head[u];
head[u] = cnt++;
}
void dfs1(int u, int f, int d)
{
dep[u] = d;
siz[u] = 1;
son[u] = 0;
fa[u] = f;
for(int i=head[u];i!=-1;i=e[i].next){
int ff = e[i].v;
if(ff==f) continue;
dfs1(ff, u, d+1);
siz[u] += siz[ff];
if(siz[son[u]] < siz[ff])
son[u] = ff;
}
}
void dfs2(int u, int tp)
{
top[u] = tp;
id[u] = ++num;
if(son[u]) dfs2(son[u], tp);
for(int i=head[u];i!=-1;i=e[i].next)
{
int ff = e[i].v;
if(ff==fa[u] || ff==son[u]) continue;
dfs2(ff, ff);
}
}
int sum[N<<2], lazy[N<<2], len[N<<2];
void push_up(int x)
{
sum[x] = sum[lson(x)] + sum[rson(x)];
}
void push_dw(int x, int l, int r)
{
if(lazy[x])
{
int mid = (l+r)>>1;
sum[lson(x)] += lazy[x] * (mid-l+1);
sum[rson(x)] += lazy[x] * (r-mid);
lazy[lson(x)] += lazy[x];
lazy[rson(x)] += lazy[x];
lazy[x] = 0;
}
}
void build(int now, int l, int r)
{
len[now] = r-l+1;
lazy[now] = 0;
if(l==r)
{
sum[now] = val[l];
return;
}
int mid = (l+r)>>1;
build(lson(now), l, mid);
build(rson(now), mid+1, r);
push_up(now);
}
int query(int now, int l, int r, int x)
{
if(l==r)
{
return sum[now];
}
int mid = (l+r) >> 1;
push_dw(now, l, r);
if(x<=mid) return query(lson(now), l, mid, x);
else return query(rson(now), mid+1, r, x);
}
void update(int x, int L, int R, int l, int r, int v)
{
if(L==l && R==r){
sum[x] += v * (R-L+1);
lazy[x] += v;
return;
}
push_dw(x, L, R);
int mid = (L+R) >> 1;
if(r<=mid) update(lson(x), L, mid, l, r, v);
else if(l>mid) update(rson(x), mid+1, R, l, r, v);
else{
update(lson(x), L, mid, l, mid, v);
update(rson(x), mid+1, R, mid+1, r, v);
}
push_up(x);
}
void TreUpdate(int u, int v, int vv)
{
int tp1 = top[u], tp2 = top[v];
while(tp1!=tp2)
{
if(dep[tp1] < dep[tp2]){
swap(tp1, tp2);
swap(u, v);
}
update(1, 1, n, id[tp1], id[u], vv);
u = fa[tp1];
tp1 = top[u];
}
if(dep[u] > dep[v]) swap(u,v);
update(1, 1, n, id[u], id[v], vv);
}
int main() {
while(~scanf("%d%d%d", &n, &m, &q))
{
ms(head, -1);
cnt = 0;
for(int i=1;i<=n;i++)
{
scanf("%d", &arr[i]);
}
int x, y;
for(int i=0;i<m;i++)
{
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
num = 0;
dfs1(1, 0, 1);
dfs2(1, 1);
for(int i=1;i<=n;i++)
val[id[i]] = arr[i];
build(1, 1, n);
int a, b, vv; getchar();
for(int i=0;i<q;i++)
{
char op[3];
cin >> op;
if(op[0]=='I')
{
scanf("%d%d%d", &a, &b, &vv);
TreUpdate(a, b, vv);
}
else if(op[0]=='D')
{
scanf("%d%d%d", &a, &b, &vv);
TreUpdate(a, b, -vv);
}
else if(op[0]=='Q')
{
scanf("%d", &a);
printf("%d\n",query(1, 1, n, id[a]));
}
getchar();
}
}
return 0;
}
树状数组
树状数组也可以实现区间更新、单点询问
比如要在区间[u, v]上加上1,就只要add(u, 1), add(v+1, -1)
询问x的值,就是getsum(x)(即前缀和)
/*
树链剖分部分略
*/
int lowbit(int x){ return x&(-x); }
int c[N], maxx;
void add2(int i, int v)
{
while(i<=n){
c[i] += v;
i += lowbit(i);
}
}
int sum(int i)
{
int s = 0;
while(i>0)
{
s += c[i];
i -= lowbit(i);
}
return s;
}
void TreUpdate(int u, int v, int vv)
{
int tp1 = top[u], tp2 = top[v];
while(tp1!=tp2)
{
if(dep[tp1] < dep[tp2]){
swap(tp1, tp2);
swap(u, v);
}
add2(id[tp1], vv);
add2(id[u]+1, -vv);
u = fa[tp1];
tp1 = top[u];
}
if(dep[u] > dep[v]) swap(u,v);
add2(id[u], vv);
add2(id[v]+1, -vv);
}
int main() {
while(~scanf("%d%d%d", &n, &m, &q))
{
ms(head, -1);
ms(c, 0);
cnt = 0;
for(int i=1;i<=n;i++)
{
scanf("%d", &arr[i]);
}
int x, y;
for(int i=0;i<m;i++)
{
scanf("%d%d", &x, &y);
add(x, y);
add(y, x);
}
num = 0;
dfs1(1, 0, 1);
dfs2(1, 1);
for(int i=1;i<=n;i++){
add2(id[i], arr[i]);
add2(id[i]+1, -arr[i]);
}
int a, b, vv; getchar();
for(int i=0;i<q;i++)
{
char op[3];
cin >> op;
if(op[0]=='I')
{
scanf("%d%d%d", &a, &b, &vv);
TreUpdate(a, b, vv);
}
else if(op[0]=='D')
{
scanf("%d%d%d", &a, &b, &vv);
TreUpdate(a, b, -vv);
}
else if(op[0]=='Q')
{
scanf("%d", &a);
printf("%d\n",sum(id[a]));
}
getchar();
}
}
return 0;
}
2. 动态树
在lct的splay树上加上lazy标记,每次更新用split分离c1,c2,在c2懒标记上加上v,待之后下传。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
int fa[maxn], ch[maxn][2], val[maxn];
bool tag[maxn];
int lazy[maxn];
int n, m, q, a[maxn];
#define ls(x) (ch[x][0])
#define rs(x) (ch[x][1])
#define fa(x) (fa[x])
inline bool ident(int x, int f) { return rs(f)==x; }
inline void connect(int x, int f,int s) { ch[f][s] = x; fa(x) = f; }
//#define update(x) spl[x].res=spl[ls(x)].res^spl[rs(x)].res^spl[x].val
inline bool nroot(int x) {return ls(fa(x))==x||rs(fa(x))==x; }
inline void rever(int x) {std::swap(ls(x), rs(x)); tag[x]^=1;}
inline int read(){
int x=0,w=0;char ch=0;
while(!isdigit(ch)) w|=ch=='-',ch=getchar();
while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return w?-x:x;
}
inline void update(int x)
{
}
inline void pushdw(int x)
{
if(tag[x])
{
if(ls(x)) rever(ls(x));
if(rs(x)) rever(rs(x));
}
tag[x] = 0;
if(lazy[x])
{
if(ls(x)) {val[ls(x)] += lazy[x]; lazy[ls(x)] += lazy[x];}
if(rs(x)) {val[rs(x)] += lazy[x]; lazy[rs(x)] += lazy[x];}
}
lazy[x] = 0;
}
void pushall(int x)
{
if(nroot(x)) pushall(fa(x));
pushdw(x);
}
inline void rotate(int x)
{
int f = fa(x), ff=fa(f), k=ident(x, f);
connect(ch[x][k^1], f, k);
fa(x)=ff;
if(nroot(f)) ch[ff][ident(f, ff)]=x;
connect(f, x, k^1);
update(f); update(x);
}
inline void splaying(int x)
{
pushall(x);
while(nroot(x))
{
int f=fa(x), ff=fa(f);
if(nroot(f)) ident(f, ff)^ident(x,f)?rotate(x):rotate(f);
rotate(x);
}
}
inline void access(int x)
{
for(int y=0;x;x=fa(x)){
splaying(x);
rs(x) = y;
update(x);
y = x;
}
}
inline void mkroot(int x)
{
access(x);
splaying(x);
rever(x);
}
inline int findroot(int x)
{
access(x);
splaying(x);
while(ls(x))
{
pushdw(x);
x = ls(x);
}
splaying(x);
return x;
}
inline void link(int x, int y)
{
mkroot(x);
if(findroot(y)!=x)
fa(x) = y;
}
inline void cut(int x, int y)
{
mkroot(x);
//如果x和y不在一棵树上,or x和y不是紧紧挨着的,就return
if(findroot(y)!=x || fa(y)!=x || ls(y)) return;
fa(y) = rs(x) = 0;
update(x);
}
inline void split(int x, int y)
{
mkroot(x);
access(y);
splaying(y);
//此时y包含整条链的信息,只需访问y即可
}
int main() {
while(~scanf("%d%d%d",&n, &m, &q)){
memset(lazy, 0, sizeof lazy);
memset(val, 0, sizeof val);
memset(tag, 0, sizeof tag);
memset(fa, 0, sizeof fa);
memset(ch, 0, sizeof ch);
for (int i = 1; i <= n; i++) {
val[i] = read();
}
int x, y;
for(int i=0;i<m;i++)
{
x = read(); y = read();
link(x, y);
}
char op[3];
while(q--)
{
scanf("%s", op);
if(op[0]=='Q'){
x = read();
access(x);
printf("%d\n", val[x]);
}
else{
x = read(); y = read();
int v = read();
if(op[0]=='D') v = -v;
split(x, y);
val[y] += v;
lazy[y] += v;
}
}
}
return 0;
}