P3979 遥远的国度
本题需要支持 3 3 3 个操作:
- 把根修改为 i d id id;
- 将 x y x\;y xy 路径上的所有点权修改为 v v v;
- 询问以 x x x 为根的子树中的最小点权。
看到这道题,第一思路就是对于每个操作
1
1
1,换根后重新剖一遍。但时间复杂度为
O
(
q
n
)
\operatorname{O}(qn)
O(qn)!直接原地爆炸
所以,我们想到另一种方法,直接以 1 1 1 为根来剖,记录当前的根,每次修改和查询的时候再根据根的位置回答。
对于修改操作,由于一棵树上任意两点间只有一条路径,所以直接修改就行了。
重点是查询:
设当前的根为 r t rt rt,要询问以 x x x 为根的子树中的最小值。
- x = r t x=rt x=rt:直接输出全局最小值;
- r t rt rt 是 x x x 的祖先:直接输出 x x x 的子树的最小值;
- x x x 是 r t rt rt 的祖先:这种情况非常 毒瘤 \color{White}\colorbox{White}{毒瘤} 毒瘤有意思!我们待会会讨论如何处理;
- 以上情况都不是,相当于 x x x 在其他的分支,也是直接输出 x x x 的子树的最小值。
判断顺序:先判断是否是 1 1 1,再判断是否是 3 3 3,剩下自然就是 2 2 2 和 4 4 4 了。
现在讨论一下如何处理情况 3 3 3:
如图,若 x = 2 , r t = 4 x=2,rt=4 x=2,rt=4,则以 4 4 4 为根时, 2 2 2 的子树就是除了往 4 4 4 的方向的节点外的所有节点。也就是要找 x x x 到 r t rt rt 这条路径上 x x x 的儿子。
找的方法可以用倍增,记录 f a ( x ) ( i ) fa(x)(i) fa(x)(i) 为 x x x 的第 2 i 2^i 2i 级祖先, getfa(x,k) \operatorname{getfa(x,k)} getfa(x,k) 函数返回 x x x 的第 2 k 2^k 2k 级祖先,实现如下:
int getfa(int x, int k)
{
for (int i = lg[k]; i >= 0; i--)
{
if (k >= (1 << i))
{
x = fa[x][i];
k -= (1 << i);
}
}
return x;
}
令 y ← getfa(x,dep[rt]-dep[x]-1) y\gets\operatorname{getfa(x,dep[rt]-dep[x]-1)} y←getfa(x,dep[rt]-dep[x]-1),我们只需判断 f a ( y ) ( 0 ) fa(y)(0) fa(y)(0) 是否等于 x x x 即可,这样既判断了 x x x 是否是 r t rt rt 的祖先,又顺便求出了 y y y。
Code \Large\text{Code} Code
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 1e5 + 5;
const int INF = 0x7fffffff;
int cnt, Time;
int b[MAXN], a[MAXN], head[MAXN], fa[MAXN][18], lg[MAXN], dep[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], top[MAXN];
struct edge
{
int to, nxt;
}e[MAXN << 1];
void add(int u, int v)
{
e[++cnt] = edge{v, head[u]};
head[u] = cnt;
}
void dfs1(int u, int father)
{
fa[u][0] = father;
for (int i = 1; i <= 17; i++)
{
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
dep[u] = dep[father] + 1;
siz[u] = 1;
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v == father)
{
continue;
}
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
{
son[u] = v;
}
}
}
void dfs2(int u, int topp)
{
dfn[u] = ++Time;
a[Time] = b[u];
top[u] = topp;
if (son[u])
{
dfs2(son[u], topp);
}
for (int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (top[v])
{
continue;
}
dfs2(v, v);
}
}
int getfa(int x, int k)
{
for (int i = lg[k]; i >= 0; i--)
{
if (k >= (1 << i))
{
x = fa[x][i];
k -= (1 << i);
}
}
return x;
}
#define lson pos << 1
#define rson pos << 1 | 1
struct tree
{
int l, r, val = INF, tag;
}t[MAXN << 2];
void pushup(int pos)
{
t[pos].val = min(t[lson].val, t[rson].val);
}
void pushdown(int pos)
{
if (t[pos].tag)
{
t[lson].val = t[lson].tag = t[rson].val = t[rson].tag = t[pos].tag;
t[pos].tag = 0;
}
}
void build(int pos, int l, int r)
{
t[pos].l = l, t[pos].r = r;
if (l == r)
{
t[pos].val = a[l];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
pushup(pos);
}
void update(int pos, int L, int R, int k)
{
int l = t[pos].l, r = t[pos].r;
if (l >= L && r <= R)
{
t[pos].val = t[pos].tag = k;
return;
}
pushdown(pos);
int mid = (l + r) >> 1;
if (L <= mid)
{
update(lson, L, R, k);
}
if (R > mid)
{
update(rson, L, R, k);
}
pushup(pos);
}
int query(int pos, int L, int R)
{
int l = t[pos].l, r = t[pos].r;
if (l >= L && r <= R)
{
return t[pos].val;
}
pushdown(pos);
int mid = (l + r) >> 1, res = INF;
if (L <= mid)
{
res = query(lson, L, R);
}
if (R > mid)
{
res = min(res, query(rson, L, R));
}
return res;
}
void update_path(int x, int y, int v)
{
while (top[x] != top[y])
{
if (dep[top[x]] < dep[top[y]])
{
swap(x, y);
}
update(1, dfn[top[x]], dfn[x], v);
x = fa[top[x]][0];
}
if (dep[x] < dep[y])
{
swap(x, y);
}
update(1, dfn[y], dfn[x], v);
}
int query_subtree(int x)
{
return query(1, dfn[x], dfn[x] + siz[x] - 1);
}
int main()
{
int n, m, u, v, rt, op, x, y;
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; i++)
{
lg[i] = lg[i >> 1] + 1;
}
for (int i = 1; i < n; i++)
{
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
for (int i = 1; i <= n; i++)
{
scanf("%d", b + i);
}
dfs1(1, 0);
dfs2(1, 1);
build(1, 1, n);
scanf("%d", &rt);
while (m--)
{
scanf("%d", &op);
if (op == 1)
{
scanf("%d", &rt);
}
else if (op == 2)
{
scanf("%d%d%d", &x, &y, &v);
update_path(x, y, v);
}
else
{
scanf("%d", &x);
if (x == rt)
{
printf("%d\n", t[1].val);
}
else if (dep[x] < dep[rt] && fa[y = getfa(rt, dep[rt] - dep[x] - 1)][0] == x)
{
if (dfn[u] + siz[u] <= n)
{
printf("%d\n", min(query(1, 1, dfn[y] - 1), query(1, dfn[y] + siz[y], n))); //y的子树不要
}
else
{
printf("%d\n", query(1, 1, dfn[y] - 1));
}
}
else
{
printf("%d\n", query_subtree(x));
}
}
}
return 0;
}
CF916E Jamie and Tree
- 给定一个点 v v v,将整颗树的根变为 v v v;
- 给定两个点 u , v u, v u,v,将 l c a ( u , v ) lca(u, v) lca(u,v) 所在的子树都加上 x x x;
- 给定一个点 v v v,回答以 v v v 所在的子树的权值和。
这道题的操作 1 1 1 和 3 3 3 都和上一题一样,只是操作 2 2 2 变成了子树修改,所以也要分类讨论:
- l c a = r t lca=rt lca=rt:直接将全局和加上 x x x;
- l c a lca lca 是 r t rt rt 的祖先:同查询,记 l c a lca lca 到 r t rt rt 这条路径上 l c a lca lca 的儿子为 s o n son son,我们可以先将全局和加上 x x x,再将 s o n son son 的子树和减去 x x x,这样相当于 s o n son son 的子树都没加。
- 其他情况下直接将 l c a lca lca 的子树和加 x x x。
但是还有一个坑,就是 l c a lca lca 也会根据 r t rt rt 的位置而改变!
- u u u 和 v v v 都在 r t rt rt 的子树内: l c a ← LCA(u,v) lca\gets\operatorname{LCA(u,v)} lca←LCA(u,v);
- 其中一个在 r t rt rt 的子树内: l c a ← r t lca\gets rt lca←rt;
- u u u 和 v v v 都不在 r t rt rt 的子树内: l c a lca lca 为 LCA(u,rt) \operatorname{LCA(u,rt)} LCA(u,rt) 和 LCA(v,rt) \operatorname{LCA(v,rt)} LCA(v,rt) 中深度较大的。
综上,可以发现,以上 3 3 3 种情况的 l c a lca lca 都满足是 LCA(u,v) , LCA(u,rt) \operatorname{LCA(u,v)},\operatorname{LCA(u,rt)} LCA(u,v),LCA(u,rt) 和 LCA(v,rt) \operatorname{LCA(v,rt)} LCA(v,rt) 中深度最大的那个。
Code \Large\text{Code} Code
#include <iostream>
#include <cstdio>
#define int long long
#define re register
using namespace std;
inline int read()
{
re int x = 0, f = 0;
re char c = getchar();
while (c < '0' || c > '9')
{
f |= c == '-';
c = getchar();
}
while (c >= '0' && c <= '9')
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
return f ? -x : x;
}
inline void write(int x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x > 9)
{
write(x / 10);
}
putchar(x % 10 ^ '0');
}
inline void swap2(int &x, int &y)
{
x ^= y ^= x ^= y;
}
inline int min2(int x, int y)
{
return x < y ? x : y;
}
//--------------------------------------------
const int MAXN = 1e5 + 5;
int cnt, Time;
int a[MAXN], b[MAXN], head[MAXN], fa[MAXN][18], lg[MAXN], dep[MAXN], siz[MAXN], son[MAXN], dfn[MAXN], top[MAXN];
struct edge
{
int to, nxt;
}e[MAXN << 1];
void add(int u, int v)
{
e[++cnt] = edge{v, head[u]};
head[u] = cnt;
}
void dfs1(int u, int father)
{
fa[u][0] = father;
dep[u] = dep[father] + 1;
siz[u] = 1;
for (re int i = 1; i <= 17; i++)
{
fa[u][i] = fa[fa[u][i - 1]][i - 1];
}
for (re int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (v == father)
{
continue;
}
dfs1(v, u);
siz[u] += siz[v];
if (siz[v] > siz[son[u]])
{
son[u] = v;
}
}
}
void dfs2(int u, int topp)
{
dfn[u] = ++Time;
a[Time] = b[u];
top[u] = topp;
if (son[u])
{
dfs2(son[u], topp);
}
for (re int i = head[u]; i; i = e[i].nxt)
{
int v = e[i].to;
if (top[v])
{
continue;
}
dfs2(v, v);
}
}
int lca(int x, int y)
{
if (dep[x] < dep[y])
{
swap2(x, y);
}
while (dep[x] > dep[y])
{
x = fa[x][lg[dep[x] - dep[y]]];
}
if (x == y)
{
return x;
}
for (re int i = 17; i >= 0; i--)
{
if (fa[x][i] != fa[y][i])
{
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
int getfa(int x, int k)
{
for (re int i = lg[k]; i >= 0; i--)
{
if (k >= (1 << i))
{
x = fa[x][i];
k -= (1 << i);
}
}
return x;
}
#define lson pos << 1
#define rson pos << 1 | 1
struct tree
{
int l, r, siz, val, tag;
}t[MAXN << 2];
void pushup(int pos)
{
t[pos].val = t[lson].val + t[rson].val;
}
void cal(int pos, int val)
{
t[pos].val += val * t[pos].siz;
t[pos].tag += val;
}
void pushdown(int pos)
{
if (t[pos].tag)
{
cal(lson, t[pos].tag);
cal(rson, t[pos].tag);
t[pos].tag = 0;
}
}
void build(int pos, int l, int r)
{
t[pos].l = l, t[pos].r = r, t[pos].siz = r - l + 1;
if (l == r)
{
t[pos].val = a[l];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid);
build(rson, mid + 1, r);
pushup(pos);
}
void update(int pos, int L, int R, int k)
{
int l = t[pos].l, r = t[pos].r;
if (L <= l && r <= R)
{
cal(pos, k);
return;
}
pushdown(pos);
int mid = (l + r) >> 1;
if (L <= mid)
{
update(lson, L, R, k);
}
if (R > mid)
{
update(rson, L, R, k);
}
pushup(pos);
}
int query(int pos, int L, int R)
{
int l = t[pos].l, r = t[pos].r;
if (L <= l && r <= R)
{
return t[pos].val;
}
pushdown(pos);
int mid = (l + r) >> 1, res = 0;
if (L <= mid)
{
res = query(lson, L, R);
}
if (R > mid)
{
res += query(rson, L, R);
}
return res;
}
void update_subtree(int x, int k)
{
update(1, dfn[x], dfn[x] + siz[x] - 1, k);
}
int query_subtree(int x)
{
return query(1, dfn[x], dfn[x] + siz[x] - 1);
}
int getlca(int x, int y, int z) //3个lca中深度最大的
{
int a = lca(x, y), b = lca(x, z), c = lca(y, z);
if (dep[a] < dep[b])
{
a = b;
}
if (dep[a] < dep[c])
{
a = c;
}
return a;
}
signed main()
{
int n = read(), q = read(), rt = 1;
for (re int i = 2; i <= n; i++)
{
lg[i] = lg[i >> 1] + 1;
}
for (re int i = 1; i <= n; i++)
{
b[i] = read();
}
for (re int i = 1; i < n; i++)
{
int u = read(), v = read();
add(u, v);
add(v, u);
}
dfs1(1, 0);
dfs2(1, 1);
build(1, 1, n);
while (q--)
{
int op = read(), u = read(), v, x, p, q;
switch (op)
{
case 1:
rt = u;
break;
case 2:
v = read(), x = read();
u = getlca(u, v, rt);
if (u == rt)
{
update(1, 1, n, x);
}
else if (lca(u, rt) == u)
{
v = getfa(rt, dep[rt] - dep[u] - 1);
update(1, 1, n, x);
update_subtree(v, -x); //将v的子树减掉
}
else
{
update_subtree(u, x);
}
break;
case 3:
if (u == rt)
{
write(query(1, 1, n));
}
else if (lca(rt, u) == u)
{
v = getfa(rt, dep[rt] - dep[u] - 1);
write(query(1, 1, n) - query_subtree(v));
}
else
{
write(query_subtree(u));
}
putchar('\n');
break;
}
}
return 0;
}