扯淡
据说这道题是CF几个月前某场比赛的题,然后被出到了前几天雅礼集训T1,于是就又成为了一道BZOJ题。
结果最后因为忘记建子文件夹光荣爆零
赛后测数据,第五个子任务在使用文件操作和不开O2的情况一个点1.5s左右轻松跑过所有测试点,更凉了。
真是雅礼集训最简单的题
Description
给定一棵树,树根初始为1,每个点有权值,有三种操作:
1.将根设为x;
2.将lca(x, y)的子树内所有点的权值加上z;
3.查询x的子树内所有点的权值和。
Input
第一行两个整数n, q,表示树的大小和操作个数。
接下来一行n个整数,为每个点的权值。
接下来n-1行,每行两个整数x, y,表示一条树边。
接下来q行,每行若干个数,表示一次操作。
Output
对于每个3操作,输出一行一个整数表示答案。
Solution
如果没有1操作,那么这就是树剖裸题。那么我们就要思考1操作对求lca和子树问题的影响。
我才不会跟你们说我在考场上脑子里想的全是遥远的国度
事实上,我没做过那个题。
那么自己yy的时候就到了。
先对最开始那颗根为1的树(称为原树)进行树剖,可以发现:
当我们要换的根不在原树中lca(x, y)的子树内时,换根对求lca和修改/查询子树都没有影响;
当换的根在lca(x, y)的子树内时,我们要求的lca(x, y)就变成了原树中lca(x, root)与lca(y, root)中深度更深的一个。
对于修改/询问子树,假设我们想得到x的子树,设y为与x相邻的且距离root最近的点(即从x到root最短路上经过的第一个点),此时新树中x的子树就是除原树中y的子树之外的所有点。那么怎么求y呢?将root向上倍增到x的深度+1的点,就是y了。
Code
#include <iostream>
#include <cstring>
#include <cstdio>
const int maxn = 3e5 + 7;
typedef long long ll;
using namespace std;
int to[maxn << 1];
int nex[maxn << 1];
int last[maxn], k;
int root;
int w[maxn];
int id[maxn], cnt;
int fa[maxn];
int son[maxn];
int dep[maxn];
int top[maxn];
int val[maxn];
int size[maxn];
int rmq[maxn][18];
ll st[maxn << 2];
ll len[maxn << 2];
ll add[maxn << 2];
inline int read()
{
int X = 0, w = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') w = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') X = X * 10 + ch - '0', ch = getchar();
return X * w;
}
inline void add_edge(int x, int y)
{
to[++k] = y; nex[k] = last[x]; last[x] = k;
}
inline void pushup(int rt)
{
st[rt] = st[rt << 1] + st[rt << 1 | 1];
}
inline void pushdown(int rt)
{
if (add[rt]) {
int ls = rt << 1, rs = ls | 1;
add[ls] += add[rt];
add[rs] += add[rt];
st[ls] += len[ls] * add[rt];
st[rs] += len[rs] * add[rt];
add[rt] = 0;
}
}
void build(int l, int r, int rt)
{
len[rt] = r - l + 1;
if (l == r) {
st[rt] = val[l];
return;
}
int mid = l + r >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
pushup(rt);
}
void update(int x, int y, int z, int l, int r, int rt)
{
if (l >= x && r <= y) {
st[rt] += len[rt] * z;
add[rt] += z;
return;
}
pushdown(rt);
int mid = l + r >> 1;
if (x <= mid) update(x, y, z, l, mid, rt << 1);
if (y > mid) update(x, y, z, mid + 1, r, rt << 1 | 1);
pushup(rt);
}
ll query(int x, int y, int l, int r, int rt)
{
if (l >= x && r <= y) return st[rt];
pushdown(rt);
int mid = l + r >> 1;
ll ans = 0;
if (x <= mid) ans += query(x, y, l, mid, rt << 1);
if (y > mid) ans += query(x, y, mid + 1, r, rt << 1 | 1);
return ans;
}
void dfs1(int x, int f, int depth)
{
dep[x] = depth;
fa[x] = f;
size[x] = 1;
int wson = -1;
for (int i = last[x]; i; i = nex[i]) {
int y = to[i];
if (y == f) continue;
dfs1(y, x, depth + 1);
size[x] += size[y];
if (size[y] > wson) wson = size[y], son[x] = y;
}
}
void dfs2(int x, int topf)
{
id[x] = ++cnt;
val[cnt] = w[x];
top[x] = topf;
if (!son[x]) return;
dfs2(son[x], topf);
for (int i = last[x]; i; i = nex[i]) {
int y = to[i];
if (y == fa[x] || y == son[x]) continue;
dfs2(y, y);
}
}
inline int lca(int x, int y)
{
while (top[x] != top[y]) {
if (dep[top[x]] < dep[top[y]]) y = fa[top[y]];
else x = fa[top[x]];
}
if (dep[x] > dep[y]) return y;
return x;
}
void dfs(int x)
{
rmq[x][0] = fa[x];
for (int i = 1; dep[x] >> i; i++)
rmq[x][i] = rmq[rmq[x][i - 1]][i - 1];
for (int i = last[x]; i; i = nex[i])
if (to[i] != fa[x]) dfs(to[i]);
}
inline int qnext(int x, int rt)
{
int depth = dep[rt] - dep[x] - 1;
for (int i = 0; i <= 17; i++)
if (depth & (1 << i)) rt = rmq[rt][i];
return rt;
}
int main(void)
{
register int n = read(), q = read();
for (int i = 1; i <= n; i++) w[i] = read();
for (int i = 1; i < n; i++) {
int x = read(), y = read();
add_edge(x, y);
add_edge(y, x);
}
root = 1;
dfs1(1, 0, 1);
dfs2(1, 1);
dfs(1);
build(1, n, 1);
while (q--) {
int opt = read(), x = read();
if (opt == 1) root = x;
else if (opt == 2) {
int y = read(), z = read();
int lca1 = lca(x, root), lca2 = lca(y, root), p;
if (lca1 == lca2) p = lca(x, y);
//当lca(x, root) = lca(y, root) = root即root在lca(x, y)子树之外时,没有影响;
else p = dep[lca1] > dep[lca2] ? lca1 : lca2;
//不然就是lca(x, root)与lca(y, root)中深度更深的一个
if (p == root) update(1, n, z, 1, n, 1);
//当lca(x, y) = root时,子树是整棵树;
else if (id[root] < id[p] || id[root] >= id[p] + size[p])
update(id[p], id[p] + size[p] - 1, z, 1, n, 1); //当root在lca(x, y)的子树之外时,没有影响,直接查询;
else {
y = qnext(p, root);
st[1] += n * z;
add[1] += z;
update(id[y], id[y] + size[y] - 1, -z, 1, n, 1);
//否则修改y的子树以外的值,y的含义见上述内容。
}
}
else {
if (x == root) printf("%lld\n", st[1]);
else if (lca(root, x) != x) printf("%lld\n", query(id[x], id[x] + size[x] - 1, 1, n, 1));
else {
int y = qnext(x, root);
printf("%lld\n", st[1] - query(id[y], id[y] + size[y] - 1, 1, n, 1));
}
//查询同修改
}
}
return 0;
}