BZOJ传送门
题目描述
在一片土地上有
个城市,通过
条无向边互相连接,形成一棵树的结构,相邻两个城市的距离为
,其中第
个城市的价值为
。
不幸的是,这片土地常常发生地震,并且随着时代的发展,城市的价值也往往会发生变动。
接下来你需要在线处理
次操作:
表示发生了一次地震,震中城市为
,影响范围为
,所有与
距离不超过
的城市都将受到影响,该次地震造成的经济损失为所有受影响城市的价值和。
表示第
个城市的价值变成了
。
为了体现程序的在线性,操作中的
、
、
都需要异或你程序上一次的输出来解密,如果之前没有输出,则默认上一次的输出为
。
输入输出格式
输入格式
第一行包含两个正整数
和
。
第二行包含
个正整数,第
个数表示
。
接下来
行,每行包含两个正整数
、
,表示
和
之间有一条无向边。
接下来
行,每行包含三个数,表示
次操作。
输出格式
包含若干行,对于每个询问输出一行一个正整数表示答案。
输入输出样例
输入样例
8 1
1 10 100 1000 10000 100000 1000000 10000000
1 2
1 3
2 4
2 5
3 6
3 7
3 8
0 3 1
输出样例
11100101
数据范围
解题分析
A了幻想乡战略游戏一题就容易发现这道题也是点分树的套路题了… 不过我们在统计答案的时候有一些小技巧。
不难想到, 对于每一个分治重心, 我们可以利用动态开点线段树或树状数组维护距离当前重心距离为
的权值和, 查询时向上跳点分树即可。 但显然这样会导致一部分点的贡献重复计算, 所以我们需要维护另一个值。
如下图, 假设我们要统计与
点距离不超过
的权值和。
我们从
点开始向上爬树。 对于重心
它所管辖的联通块深度为
, 所以我们可以得到
的权值和。
在点分树上的父节点是
。 我们可以利用
_
做到O(1)查询
间距离为2。显然我们需要将距离
点距离为
的点权值和统计出来, 但这样会重复计算
点的权值。 进一步观察发现, 只有上层分治重心到下层分治重心方向上的点会被重复计算, 这样我们就可以另开一个线段树或树状数组记录子联通块到上层分治重心距离为
的权值和。
博主用的是树状数组动态开点, 比起旁边dalao的动态开点线段树大概是这样的:
可以看出来无论在空间还是在时间方面树状数组都占优势。
另外还有一个值得留意的细节:上层的分治重心并不一定比下层的重心距离统计点远, 所以一定要在点分树上跳至最上层(否则会WA进而导致RE)。
总复杂度
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <cctype>
#include <cmath>
#define W while
#define IN inline
#define gc getchar()
#define R register
#define MX 200050
#define lowbit(i) (i & -i)
bool neg;
template <class T>
IN void in(T &x)
{
neg = x = 0; R char c = gc;
W (!isdigit(c))
{
if(c == '-') neg = true; c = gc;
}
W (isdigit(c))
x = (x << 1) + (x << 3) + c - 48, c = gc;
if(neg) x = -x;
}
int *tree_G[MX], *tree_Up[MX];
int head[MX], gnex[MX], dep[MX], RMQ[19][MX << 1], val[MX], mx[MX], dat[MX],
siz[MX], eul[MX << 1], fir[MX], fat[MX], lg[MX << 1], dpmx[MX], upmx[MX];
//蒟蒻用的是int动态new数组的方法避免树状数组爆空间, 也可以用vector resize的方法。
//dpmx表示当前分治联通块到分治重心最远的点的深度
//upmx表示当前分治联通块中到上层分治重心最远的点的距离
//tree_G存当前联通块信息, tree_Up存到上层重心的信息
int dot, q, cot, cnt, root, deal;
bool vis[MX];
struct Edge
{
int to, nex;
}edge[MX << 1];
namespace LCA//RMQ_LCA降低常数
{
IN void addedge(const int &from, const int &to)
{edge[++cnt] = {to, head[from]}; head[from] = cnt;}
void DFS(const int &now, const int &fa)
{
eul[fir[now] = ++cot] = now;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fa) continue;
dep[edge[i].to] = dep[now] + 1;
DFS(edge[i].to, now);
eul[++cot] = now;
}
}
void getlog()
{
lg[0] = -1;
for (R int i = 1; i <= cot; ++i) lg[i] = lg[i >> 1] + 1;
}
void getst()
{
R int step, bd, pre, lat;
for (R int i = 1; i <= cot; ++i) RMQ[0][i] = eul[i];
for (R int i = 1; i <= 18; ++i)
{
step = 1 << i - 1, bd = cot - (1 << i) + 1;
for (R int j = 1; j <= bd; ++j)
{
pre = RMQ[i - 1][j], lat = RMQ[i - 1][j + step];
RMQ[i][j] = dep[pre] < dep[lat] ? pre : lat;
}
}
}
IN int query(const int &x, const int &y)
{
int pre = fir[x], lat = fir[y];
if(pre > lat) std::swap(pre, lat);
int jp = lg[lat - pre + 1];
int ans1 = RMQ[jp][pre], ans2 = RMQ[jp][lat - (1 << jp) + 1];
int lca = dep[ans1] < dep[ans2] ? ans1 : ans2;
return dep[x] + dep[y] - (dep[lca] << 1);
}
}
namespace Bit_Tree
{
IN void add_G(const int &pos, R int now, const int &del, const int &bd)
{
if(!now) return tree_G[pos][now] += del, void();
W (now <= bd) tree_G[pos][now] += del, now += lowbit(now);
}
IN int query_G(const int &pos, R int now)
{
int ret = 0;
W (now) ret += tree_G[pos][now], now -= lowbit(now);
return ret + tree_G[pos][0];//自己也要算在里面
}
IN void add_Up(const int &pos, R int now, const int &del, const int &bd)
{
if(!now) return tree_Up[pos][now] += del, void();
W (now <= bd) tree_Up[pos][now] += del, now += lowbit(now);
}
IN int query_Up(const int &pos, R int now)
{
int ret = 0;
W (now) ret += tree_Up[pos][now], now -= lowbit(now);
return ret + tree_Up[pos][0];
}
}
namespace Dtdv
{
IN void getroot(const int &now, const int &fa)//确定下层分治重心
{
mx[now] = 0;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(edge[i].to == fa || vis[edge[i].to]) continue;
getroot(edge[i].to, now);
mx[now] = std::max(siz[edge[i].to], mx[now]);
}
mx[now] = std::max(deal - siz[now], mx[now]);
if(mx[now] < mx[root]) root = now;
}
int DFS(const int &now, const int &fa, const int &dp)//每次DFS求出子树大小以及深度, 方便确定树状数组大小
{
siz[now] = 1, dpmx[now] = dp;
for (R int i = head[now]; i; i = edge[i].nex)
{
if(vis[edge[i].to] || edge[i].to == fa) continue;
siz[now] += DFS(edge[i].to, now, dp + 1);
dpmx[now] = std::max(dpmx[now], dpmx[edge[i].to]);
}
return siz[now];
}
void build(R int now, const int &fa)
{
vis[now] = true; fat[now] = fa; dpmx[now] = 0;
for (int i = head[now]; i; i = edge[i].nex)
{
if(vis[edge[i].to]) continue;
deal = mx[root = 0] = DFS(edge[i].to, 0, 1);
getroot(edge[i].to, 0);
dpmx[now] = std::max(dpmx[now], dpmx[edge[i].to]);
tree_Up[root] = new int[dpmx[edge[i].to] + 5]();//动态开数组, 后面括号表示默认构造函数清零new出的数组
upmx[root] = dpmx[edge[i].to];
build(root, now);
}
tree_G[now] = new int[dpmx[now] + 5]();
}
IN void modify(R int now, const int &tar)
{
R int del = tar - val[now], dis, st = now;
val[now] = tar;
tree_G[now][0] = tar;//先改自己
W (fat[now])
{
Bit_Tree::add_G(fat[now], LCA::query(fat[now], st), del, dpmx[fat[now]]);
Bit_Tree::add_Up(now, LCA::query(fat[now], st), del, upmx[now]);
now = fat[now];
}
}
IN int query(R int now, const int &kth)
{
int limit, pre, ans = 0, dis, tmp = now, range;
ans = Bit_Tree::query_G(now, std::min(dpmx[now], kth));
//树状数组存在爆数组的情况, 可能查询的范围大于联通块深度, 所以随时取min
W (fat[now])
{
dis = LCA::query(fat[now], tmp);
if(dis > kth) {now = fat[now]; continue;}//一定是continue而不是break
range = std::min(kth - dis, dpmx[fat[now]]);
ans += Bit_Tree::query_G(fat[now], range);
ans -= Bit_Tree::query_Up(now, std::min(kth - dis, upmx[now]));
now = fat[now];
}
return ans;
}
}
int main(void)
{
int a, b, c, lastans = 0;
in(dot), in(q);
for (R int i = 1; i <= dot; ++i) in(dat[i]);
for (R int i = 1; i < dot; ++i) in(a), in(b), LCA::addedge(a, b), LCA::addedge(b, a);
LCA::DFS(1, 0);
LCA::getlog();
cnt = 0;
LCA::getst();
mx[root = 0] = deal = Dtdv::DFS(1, 0, 1);
Dtdv::getroot(1, 0);
int rt = root;
Dtdv::build(rt, 0);
root = rt;
for (R int i = 1; i <= dot; ++i)
Dtdv::modify(i, dat[i]);
W (q--)
{
in(a), in(b), in(c);
if(a & 1) Dtdv::modify(lastans ^ b, lastans ^ c);
else lastans = Dtdv::query(lastans ^ b, lastans ^ c), printf("%d\n", lastans);
}
}