【luogu P4338】【LOJ 2434】历史(LCT)
历史
题目链接:luogu P4338 / LOJ 2434
题目大意
给你一棵树,其中 1 是根,然后给你每个点的 access 次数,要你规划一个 access 的顺序,使得轻重链的切换次数最大。
然后要支持修改,修改会增加一个点的 access 次数。
思路
考虑不修改的时候怎么做。
考虑 DP 求一个子树的最大切换次数。
那除了内部贡献,还有最上面的点,那这个点最好是轮流来,除了第一次都有一个贡献,即 \(sum_i-1\)。(\(sum_i\) 是子树大小)
那怎么时候会不能有这种贡献,就是当最大的那个子树个数超过了一半,具体来讲是 \(2*maxn>sum_i\)。
那这个时候,你只能尽可能的交错,所以贡献只有 \(2*(sum_i-maxn)\)。
然后你再考虑带修改。
那带修改我们如果只有一种贡献方法就好办,但有两种的话就考虑能不能区分开。
而且通过观察我们会发现,因为是加次数,所以它作为最大值的话就只会从第一种到第二种,如果不是最大值可能会让之前的最大值由第二种变回第一种。
那你考虑用 LCT 的实边虚边维护,就只需要枚举到 \(1\) 路径上的虚边来查看是否要改动。
那这个我们可以变形一下 access 操作来得到。
然后你每次就查看是否要让实边取消,再看是否要让虚边连上,然后维护一些值就可以了。
代码
#include<cstdio>
#define ll long long
using namespace std;
struct node {
int to, nxt;
}e[800001];
int n, m;
int le[400001], KK, x, y;
ll a[400001], ans;
int re; char c;
int read() {
re = 0; c = getchar();
while (c < '0' || c > '9') c = getchar();
while (c >= '0' && c <= '9') {
re = (re << 3) + (re << 1) + c - '0';
c = getchar();
}
return re;
}
int write(ll x) {
if (x > 9ll) write(x / 10);
putchar(x % 10 + '0');
}
struct LCT {
int ls[400001], rs[400001];
int fa[400001];
ll sum[400001], val[400001], xs[400001];
//sum 是子树的匹配个数,val 相当于 a,xs 是虚边连着的大小
void work(int now) {//建出一开始的
ll maxn = val[now]; int pl = 0;
sum[now] = val[now];
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != fa[now]) {
fa[e[i].to] = now;
work(e[i].to);
if (sum[e[i].to] > maxn) maxn = sum[e[i].to], pl = e[i].to;
sum[now] += sum[e[i].to];
}
if (2ll * maxn > sum[now]) ans += 2ll * (sum[now] - maxn), rs[now] = pl;
else ans += sum[now] - 1;
xs[now] = sum[now] - val[now] - sum[rs[now]];
}
bool nrt(int now) {
return ls[fa[now]] == now || rs[fa[now]] == now;
}
bool lrs(int now) {
return ls[fa[now]] == now;
}
void up(int now) {
sum[now] = sum[ls[now]] + sum[rs[now]] + val[now] + xs[now];
}
void rotate(int x) {
int y = fa[x], z = fa[y];
int b = lrs(x) ? rs[x] : ls[x];
if (z && nrt(y)) (lrs(y) ? ls[z] : rs[z]) = x;
if (lrs(x)) rs[x] = y, ls[y] = b;
else ls[x] = y, rs[y] = b;
fa[x] = z; fa[y] = x;
if (b) fa[b] = y;
up(y);
}
void Splay(int x) {
while (nrt(x)) {
if (nrt(fa[x])) {
if (lrs(x) == lrs(fa[x])) rotate(fa[x]);
else rotate(x);
}
rotate(x);
}
up(x);
}
ll clac(int now, ll summ, ll maxn) {
if (rs[now]) return 2ll * (summ - maxn);
if (val[now] * 2ll > summ) return 2ll * (summ - val[now]);
return summ - 1;
}
void access(int now, ll y) {
Splay(now);
//一开始的那一段要特殊处理,因为是加 val 不是 xs
ll summ = sum[now] - sum[ls[now]], maxn = sum[rs[now]];
ans -= clac(now, summ, maxn);
sum[now] += y; val[now] += y;
summ += y;
if (2ll * maxn <= summ) xs[now] += maxn, rs[now] = 0, maxn = 0;
ans += clac(now, summ, maxn);
up(now);
int lst = now; now = fa[now];
for (; now; now = fa[now]) {
Splay(now);
summ = sum[now] - sum[ls[now]]; maxn = sum[rs[now]];
ans -= clac(now, summ, maxn);
sum[now] += y; xs[now] += y;
summ += y;
if (2ll * maxn <= summ) xs[now] += maxn, rs[now] = 0, maxn = 0;
if (2ll * sum[lst] > summ) xs[now] -= sum[lst], rs[now] = lst, maxn = sum[lst];
ans += clac(now, summ, maxn);
up(now);
lst = now;
}
}
}T;
void add(int x, int y) {
e[++KK] = (node){y, le[x]}; le[x] = KK;
e[++KK] = (node){x, le[y]}; le[y] = KK;
}
int main() {
// freopen("read.txt", "r", stdin);
// freopen("write.txt", "w", stdout);
n = read(); m = read();
for (int i = 1; i <= n; i++) a[i] = 1ll * read(), T.val[i] = a[i];
for (int i = 1; i < n; i++) {
x = read(); y = read();
add(x, y);
}
T.work(1);
write(ans);
putchar('\n');
while (m--) {
x = read(); y = read();
a[x] += y;
T.access(x, y);
write(ans);
putchar('\n');
}
return 0;
}