@loj - 2434@ 「ZJOI2018」历史

时间:2021-07-13 21:51:44


@description@

九条可怜是一个热爱阅读的女孩子。

这段时间,她看了一本非常有趣的小说,这本小说的架空世界引起了她的兴趣。

这个世界有 n 个城市,这 n 个城市被恰好 n-1 条双向道路联通,即任意两个城市都可以互相到达。同时城市 1 坐落在世界的中心,占领了这个城市就称霸了这个世界。

在最开始,这 n 个城市都不在任何国家的控制之下,但是随着社会的发展,一些城市会崛起形成国家并夺取世界的霸权。为了方便,我们标记第 i 个城市崛起产生的国家为第 i 个国家。 在第 i 个城市崛起的过程中,第 i 个国家会取得城市 i 到城市 1 路径上所有城市的控制权。

新的城市的崛起往往意味着战争与死亡,若第 i 个国家在崛起中,需要取得一个原本被国家 j(j ≠ i) 控制的城市的控制权,那么国家 i 就必须向国家 j 宣战并进行战争。

现在,可怜知道了,在历史上,第 i 个城市一共崛起了 ai 次。但是这些事件发生的相对顺序已经无从考究了,唯一的信息是,在一个城市崛起称霸世界之前,新的城市是不会崛起的。

战争对人民来说是灾难性的。可怜定义一次崛起的灾难度为崛起的过程中会和多少不同的国家进行战争(和同一个国家进行多次战争只会被计入一次)。可怜想要知道,在所有可能的崛起顺序中,灾难度之和最大是多少。

同时,在考古学家的努力下,越来越多的历史资料被发掘了出来,根据这些新的资料,可怜会对 ai 进行一些修正。具体来说,可怜会对 ai 进行一些操作,每次会将 ax 加上 w。她希望在每次修改之后,都能计算得到最大的灾难度。

然而可怜对复杂的计算并不感兴趣,因此她想让你来帮她计算一下这些数值。

对题面的一些补充:

同一个城市多次崛起形成的国家是同一个国家,这意味着同一个城市连续崛起两次是不会和任何国家开战的:因为这些城市原来就在它的控制之下。

在历史的演变过程中,第 i 个国家可能会有一段时间没有任何城市的控制权。但是这并不意味着第 i 个国家灭亡了,在城市 i 崛起的时候,第 i 个国家仍然会取得 1 到 i 路径上的城市的控制权。

输入格式

第一行输入两个整数 n,m 表示城市个数和操作个数。

第二行输入 n 个整数表示 ai 的初始值。 接下来 n − 1 行,每行输入两个整数 ui, vi(1≤ui,vi≤n) 描述了一条道路。

接下来 m 行每行输入两个整数 xi, wi 表示将 a[xi] 加上 wi。

输出格式

输出共 m+1 行,第一行表示初始的 ai 的答案,接下来 m 行每行表示这次修正后的答案。

样例输入

5 3

1 1 1 1 1

1 2

1 3

2 4

2 5

2 1

3 1

4 1

样例输出

6

7

9

10

样例解释

在修正开始之前,如果按照所在城市 4, 1, 5, 3, 2 的顺序崛起,那么依次会和 0, 1, 2, 1, 2 个 国家进行战争。

这时一共会产生 6 对敌对关系。可以证明这是所有崛起顺序中的最大值。

数据范围与提示

n, m <= 4*10^5。

@solution@

就是一个 link-cut-tree 中 access 过程经过的颜色段的种类数。等价地,我们统计往上爬的过程颜色第一次的出现位置个数。

怎么让它最大呢?我们不妨考虑每一个点 x 的贡献。如果相邻操作的两个点来自 x 的相同子树,则点 x 不会产生贡献。

也就是说,x 是否产生贡献只跟操作的点 p 在 x 的哪个子树有关(也可以是 x 本身),与 p 本身是什么无关。

记 sum[x] 为以 x 为根的子树内 ai 之和,再记 mxs[x] = max{max{sum[u]}, a[x]}。

当 2*mxs[x] <= sum[x] 时,可以安排 sum[x] 这些操作相邻的两两不来自于同一子树,此时最大贡献为 sum[x] - 1;否则,此时最大贡献为 2*(sum[x] - mxs[x])。

注意第一次不算,因为第一次的时候 x 还没有颜色。

这样子,我们就可以算出没有修改的贡献。

考虑怎么在修改中维护这个值,因为它本身就是一个 access 操作,所以不难想到使用 lct。

当 2*mxs[x] > sum[x] 时,如果同时让 sum[x] 与 mxs[x] 加 w,则不等式依然不变,且贡献依然不变。

于是:我们令 mxs[x] 对应的儿子(注意 mxs[x] 还可能对应 x 自己,这时就不考虑)为 x 的重儿子,然后 link-cut-tree 一波。

此时轻边要考虑轻重边的变化是否满足要求,不能像平时的 lct 一样直接就重边接上去。重链用 tag 维护一下即可。

复杂度?每次跳轻边,sum 至少翻倍,所以最多跳 O(log) 次轻边。

其实说是 link-cut-tree,不如说是树链剖分的动态维护。。。

@accepted code@

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 400000;
ll ans;
struct link_cut_tree{
struct node{
ll sum, mxs, tg, key;
node *ch[2], *fa;
int dir() {return fa->ch[1] == this;}
}pl[MAXN + 5], *ncnt, *NIL;
void maintain(node *x, ll k) {
if( x == NIL ) return ;
x->tg += k, x->sum += k, x->mxs += k;
}
void pushdown(node *x) {
if( x->tg ) {
maintain(x->ch[0], x->tg);
maintain(x->ch[1], x->tg);
x->tg = 0;
}
}
link_cut_tree() {
ncnt = NIL = &pl[0];
NIL->ch[0] = NIL->ch[1] = NIL->fa = NIL;
NIL->sum = NIL->mxs = NIL->tg = 0;
}
node *newnode() {
node *p = (++ncnt);
p->ch[0] = p->ch[1] = p->fa = NIL;
p->sum = p->mxs = p->tg = 0;
return p;
}
void set_child(node *x, node *y, int d) {
if( x != NIL ) x->ch[d] = y;
if( y != NIL ) y->fa = x;
}
bool is_root(node *x) {
return x->fa->ch[0] != x && x->fa->ch[1] != x;
}
void rotate(node *x) {
node *y = x->fa; int d = x->dir();
pushdown(y), pushdown(x);
if( is_root(y) ) x->fa = y->fa;
else set_child(y->fa, x, y->dir());
set_child(y, x->ch[!d], d);
set_child(x, y, !d);
}
void splay(node *x) {
pushdown(x);
while( !is_root(x) ) {
node *y = x->fa;
if( is_root(y) ) rotate(x);
else {
if( y->dir() == x->dir() )
rotate(y);
else rotate(x);
rotate(x);
}
}
}
node *find(node *x) {
if( x == NIL ) return NIL;
node *y = x;
while( y->ch[0] != NIL ) pushdown(y), y = y->ch[0];
splay(y);
return y;
}
void update(node *x, int k) {
node *y = NIL; x->key += k;
// printf("! %d\n", k);
while( x != NIL ) {
splay(x);
if( x->sum ) ans -= min(x->sum - 1, 2*(x->sum - x->mxs));
x->sum += k;
if( 2*x->mxs <= x->sum )
x->ch[1] = NIL;
y = find(y);
if( x->mxs < y->sum ) {
x->mxs = y->sum;
if( 2*x->mxs > x->sum )
x->ch[1] = y;
}
if( x->mxs < x->key ) {
x->ch[1] = NIL;
x->mxs = x->key;
}
maintain(x->ch[0], k);
// printf("%d : %lld %lld \t | \t %d : %lld %lld\n", x - pl, x->mxs, x->sum, y - pl, y->mxs, y->sum);
ans += min(x->sum - 1, 2*(x->sum - x->mxs));
y = x, x = x->fa;
}
// printf("%lld\n", ans);
}
}T;
vector<int>G[MAXN + 5];
void addedge(int u, int v) {
G[u].push_back(v), G[v].push_back(u);
}
int a[MAXN + 5];
link_cut_tree::node *nd[MAXN + 5];
void dfs(int x, int f) {
nd[x] = T.newnode();
if( x != 1 ) nd[x]->fa = nd[f];
T.update(nd[x], a[x]);
for(int i=0;i<G[x].size();i++) {
int p = G[x][i];
if( p != f ) dfs(p, x);
}
}
int main() {
int n, m; scanf("%d%d", &n, &m);
for(int i=1;i<=n;i++)
scanf("%d", &a[i]);
for(int i=1;i<n;i++) {
int u, v; scanf("%d%d", &u, &v);
addedge(u, v);
}
dfs(1, 0);
printf("%lld\n", ans);
for(int i=1;i<=m;i++) {
int x, w; scanf("%d%d", &x, &w);
T.update(nd[x], w);
printf("%lld\n", ans);
}
}

@details@

splay 的父子关系并不对应原树中的父子关系!!!

函数参数列表中不要把 long long 写成 int!!!!