作者: KSkun

[IOI2011]Dancing Elephants 题解

[IOI2011]Dancing Elephants 题解

题目地址:CodeChef:Elephant | CodeChef

题目描述

Pattaya 的大象跳舞表演非常有名。整个表演中,N 只大象站成一排跳舞。
经过多年的训练,大象们能够在表演时做很多迷人的舞蹈动作。表演由一系列的动作组成。在每个动作中,只有一只大象可能会移动到一个新的位置上。
大象表演的组织者想要拍摄一本包括全部动作的相册。在每个动作之后,他们要拍摄到所有的大象。
在表演中的任何时刻,多只大象可能站在同一个位置上。在这种情况下,在同一个位置上的大象会从前到后站成一排。
一架相机的拍摄宽度为 L(包括两个端点),即一架相机可以拍摄到位于连续的 L+1 个位置上的大象(有些位置可能没有大象)。因为舞台比较大,所以需要多架相机才能同时拍摄到所有的大象。
在这个题目中,你需要确定每一个动作之后,至少需要多少架相机才能同时拍摄到全部的大象。注意,所需相机的最小数目会随着动作的进行而增加、减少或者保持不变。

输入输出格式

输入格式:
第一行N、M、L。
下面N行表示大象的初始位置。
下面M行一行两个整数,第i只大象到了Pi位置。

输出格式:
每次动作后输出最少相机数。

输入输出样例

输入样例#1:

4 10 5
10
15
17
20
2 16
1 25
3 35
0 38
2 0

输出样例#1:

1
2
2
2
3

输入样例#2:

2 12321 3
2
123
1 76543
0 66321
0 78754

输出样例#2:

2
1
1

说明

1≤N≤150000

题解

如果不改变大象的位置,我们考虑怎么解决。我们从第一只大象开始,每次设置一只摄像机来覆盖L距离内的大象,然后往后找第一只没有被覆盖的大象,再设置相机。最后统计设置了多少相机即可。
现在我们需要改变大象的位置,又不能每一次全部扫一遍,自然需要一个快速统计答案的方法。首先我们考虑一种建图方法,先将所有可能出现大想的位置离散化出来,对于相邻位置连边。每增加一只大象,切断该点与后面的点的边,并且连边至超过L距离的第一个点。将有大象的点的点权设置为1,没有的为0,统计起点到终点的路径点权和即为答案。我们考虑可以用LCT来维护这个图,因为上面不可能有环,每次实际上维护的是一棵树。LCT的功能能够满足我们的需求。

代码

以下代码参考了这个Solution:Solution: 16828126 | CodeChef,感谢原作者kazuma。

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <algorithm>
#include <vector>

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 1000005, INF = 1e9;

struct LCTNode {
    int ch[2], fa, val, sum;
    bool rev;
} lct[MAXN];

inline bool isleft(int p) {
    return lct[lct[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    register int fa = lct[p].fa;
    return lct[fa].ch[0] != p && lct[fa].ch[1] != p;
}

inline void update(int p) {
    register int ls = lct[p].ch[0], rs = lct[p].ch[1];
    lct[p].sum = lct[p].val + lct[ls].sum + lct[rs].sum;
}

inline void reverse(int p) {
    std::swap(lct[p].ch[0], lct[p].ch[1]);
    lct[p].rev ^= 1;
}

inline void pushdown(int p) {
    register int ls = lct[p].ch[0], rs = lct[p].ch[1];
    if(lct[p].rev) {
        if(ls) reverse(ls);
        if(rs) reverse(rs);
        lct[p].rev ^= 1;
    }
}

int sta[MAXN], stop;

inline void pushto(int p) {
    stop = 0;
    while(!isroot(p)) {
        sta[stop++] = p;
        p = lct[p].fa;
    }
    pushdown(p);
    while(stop) {
        pushdown(sta[--stop]);
    }
}

inline void rotate(int p) {
    register bool t = !isleft(p); register int fa = lct[p].fa, ffa = lct[fa].fa;
    lct[p].fa = ffa; if(!isroot(fa)) lct[ffa].ch[!isleft(fa)] = p;
    lct[fa].ch[t] = lct[p].ch[!t]; lct[lct[fa].ch[t]].fa = fa;
    lct[p].ch[!t] = fa; lct[fa].fa = p;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(register int fa = lct[p].fa; !isroot(p); rotate(p), fa = lct[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
    update(p);
}

inline void access(int p) {
    for(register int q = 0; p; q = p, p = lct[p].fa) {
        splay(p);
        lct[p].ch[1] = q;
        update(p);
    }
}

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

inline int findrt(int p) {
    access(p);
    splay(p);
    while(lct[p].ch[0]) p = lct[p].ch[0];
    return p;
}

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

inline void link(int u, int v) {
    split(u, v);
    lct[u].fa = v;
}

inline void cut(int u, int v) {
    split(u, v);
    if(lct[v].ch[0] != u || lct[lct[v].ch[0]].ch[1]) return;
    lct[u].fa = lct[v].ch[0] = 0;
}

inline int query(int u, int v) {
    split(u, v);
    return lct[v].sum;
}

inline void modify(int u, int w) {
    access(u);
    splay(u);
    lct[u].val = w;
    update(u);
}

int n, l, m, p[MAXN], x[MAXN], y[MAXN], cnt[MAXN];
std::vector<int> vec;

int main() {
    n = readint(); l = readint(); m = readint();
    vec.push_back(-1);
    vec.push_back(2e9);
    for(int i = 1; i <= n; i++) {
        p[i] = readint();
        vec.push_back(p[i]);
        vec.push_back(p[i] + l + 1);
    }
    for(int i = 1; i <= m; i++) {
        x[i] = readint() + 1; y[i] = readint();
        vec.push_back(y[i]);
        vec.push_back(y[i] + l + 1);
    }
    std::sort(vec.begin(), vec.end());
    vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
    int siz = vec.size();
    for(int i = 2; i <= siz; i++) {
        link(i - 1, i);
    }
    for(int i = 1; i <= n; i++) {
        int u = std::lower_bound(vec.begin(), vec.end(), p[i]) - vec.begin(),
            v = std::lower_bound(vec.begin(), vec.end(), p[i] + l + 1) - vec.begin();
        if(!cnt[u]) {
            cut(u, u + 1);
            link(u, v);
            modify(u, 1);
        }
        cnt[u]++;
    }
    for(int i = 1; i <= m; i++) {
        int ou = std::lower_bound(vec.begin(), vec.end(), p[x[i]]) - vec.begin(),
            ov = std::lower_bound(vec.begin(), vec.end(), p[x[i]] + l + 1) - vec.begin(),
            u = std::lower_bound(vec.begin(), vec.end(), y[i]) - vec.begin(),
            v = std::lower_bound(vec.begin(), vec.end(), y[i] + l + 1) - vec.begin();
        cnt[ou]--;
        if(!cnt[ou]) {
            cut(ou, ov);
            link(ou, ou + 1);
            modify(ou, 0);
        }
        if(!cnt[u]) {
            cut(u, u + 1);
            link(u, v);
            modify(u, 1);
        }
        cnt[u]++;
        p[x[i]] = y[i];
        printf("%d\n", query(1, siz));
    }
    return 0;
}
[HNOI2010]弹飞绵羊 题解

[HNOI2010]弹飞绵羊 题解

题目地址:洛谷:【P3203】[HNOI2010]弹飞绵羊 – 洛谷、BZOJ:Problem 2002. — [Hnoi2010]Bounce 弹飞绵羊

题目描述

某天,Lostmonkey发明了一种超级弹力装置,为了在他的绵羊朋友面前显摆,他邀请小绵羊一起玩个游戏。游戏一开始,Lostmonkey在地上沿着一条直线摆上n个装置,每个装置设定初始弹力系数ki,当绵羊达到第i个装置时,它会往后弹ki步,达到第i+ki个装置,若不存在第i+ki个装置,则绵羊被弹飞。绵羊想知道当它从第i个装置起步时,被弹几次后会被弹飞。为了使得游戏更有趣,Lostmonkey可以修改某个弹力装置的弹力系数,任何时候弹力系数均为正整数。

输入输出格式

输入格式:
第一行包含一个整数n,表示地上有n个装置,装置的编号从0到n-1。
接下来一行有n个正整数,依次为那n个装置的初始弹力系数。
第三行有一个正整数m,
接下来m行每行至少有两个数i、j,若i=1,你要输出从j出发被弹几次后被弹飞,若i=2则还会再输入一个正整数k,表示第j个弹力装置的系数被修改成k。

输出格式:
对于每个i=1的情况,你都要输出一个需要的步数,占一行。

输入输出样例

输入样例#1:

4
1 2 1 1
3
1 1
2 1 1
1 1

输出样例#1:

2
3

说明

对于20%的数据n,m<=10000,对于100%的数据n<=200000,m<=100000

题解

我们可以考虑把第i个弹力装置和第i+ki个连边,这样我们会建出来一棵树。如果跳超过了就直接连到n+1号点上。如果不改ki的值,答案即为以n+1为根的有根树中i号点的深度。现在要改ki的值,就得动态维护树上信息,就得用LCT。改的时候,切掉i和i+ki的边,然后再加i和新i+ki的边即可。统计信息就统计子树大小。把i到n+1的链split出来以后,Splay里就只有这条链,因此子树大小就是答案。

代码

// Code by KSkun, 2018/3
#include <cstdio>

#include <algorithm>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF 
        : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

const int MAXN = 200005;

// link/cut tree

struct Node {
    int ch[2], fa, sum;
    bool rev;
} tr[MAXN];

inline bool isleft(int p) {
    return tr[tr[p].fa].ch[0] == p;
}

inline bool isroot(int p) {
    int fa = tr[p].fa;
    return tr[fa].ch[0] != p && tr[fa].ch[1] != p;
}

inline void update(int p) {
    int ls = tr[p].ch[0], rs = tr[p].ch[1];
    tr[p].sum = tr[ls].sum + tr[rs].sum + 1;
}

inline void reverse(int p) {
    std::swap(tr[p].ch[0], tr[p].ch[1]);
    tr[p].rev ^= 1;
}

inline void pushdown(int p) {
    int ls = tr[p].ch[0], rs = tr[p].ch[1];
    if(tr[p].rev) {
        if(ls) reverse(ls);
        if(rs) reverse(rs);
        tr[p].rev ^= 1;
    }
}

int sta[MAXN], stop;

inline void pushto(int p) {
    stop = 0;
    while(!isroot(p)) {
        sta[stop++] = p;
        p = tr[p].fa;
    } 
    pushdown(p);
    while(stop) {
        pushdown(sta[--stop]);
    }
}

inline void rotate(int p) {
    bool t = !isleft(p); int fa = tr[p].fa, ffa = tr[fa].fa;
    tr[p].fa = ffa; if(!isroot(fa)) tr[ffa].ch[!isleft(fa)] = p;
    tr[fa].ch[t] = tr[p].ch[!t]; tr[tr[fa].ch[t]].fa = fa;
    tr[p].ch[!t] = fa; tr[fa].fa = p;
    update(fa);
}

inline void splay(int p) {
    pushto(p);
    for(int fa = tr[p].fa; !isroot(p); rotate(p), fa = tr[p].fa) {
        if(!isroot(fa)) rotate(isleft(fa) == isleft(p) ? fa : p);
    }
    update(p);
}

inline void access(int p) {
    for(int q = 0; p; q = p, p = tr[p].fa) {
        splay(p);
        tr[p].ch[1] = q;
        update(p);
    }
}

inline void makert(int p) {
    access(p);
    splay(p);
    reverse(p);
}

inline int findrt(int p) {
    access(p);
    splay(p);
    while(tr[p].ch[0]) p = tr[p].ch[0];
    return p;
}

inline void split(int u, int v) {
    makert(u);
    access(v);
    splay(v);
}

inline void link(int u, int v) {
    split(u, v);
    tr[u].fa = v;
}

inline void cut(int u, int v) {
    split(u, v);
    if(tr[v].ch[0] != u || tr[tr[v].ch[0]].ch[1]) return;
    tr[u].fa = tr[v].ch[0] = 0;
}

int n, m, k[MAXN], op, ut, wt;

int main() {
    n = readint();
    tr[n + 1].sum = 1;
    for(int i = 1; i <= n; i++) {
        k[i] = readint();
        tr[i].sum = 1;
    }
    for(int i = 1; i <= n; i++) {
        int x = i, y = i + k[i];
        y = std::min(y, n + 1);
        link(x, y);
    }
    m = readint();
    while(m--) {
        op = readint(); ut = readint() + 1;
        if(op == 1) {
            split(ut, n + 1);
            printf("%d\n", tr[n + 1].sum - 1);
        } else {
            wt = readint();
            cut(ut, std::min(ut + k[ut], n + 1));
            k[ut] = wt;
            link(ut, std::min(ut + k[ut], n + 1));
        }
    }
    return 0;
}
[ZJOI2007]捉迷藏 题解

[ZJOI2007]捉迷藏 题解

题目地址:洛谷:【P2056】[ZJOI2007]捉迷藏 – 洛谷、BZOJ:Problem 1095. — [ZJOI2007]Hide 捉迷藏

题目描述

Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋子都互相可达。
游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。
我们将以如下形式定义每一种操作:

  • C(hange) i 改变第i个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
  • G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。

输入输出格式

输入格式:
第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。
接下来N-1行每行两个整数a, b,表示房间a与房间b之间有一条走廊相连。
接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如上文所示。

输出格式:
对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出0;若所有房间的灯都开着,输出-1。

输入输出样例

输入样例#1:

8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G

输出样例#1:

4
3
3
4

说明

对于20%的数据, N ≤50, M ≤100;
对于60%的数据, N ≤3000, M ≤10000; 对于100%的数据, N ≤100000, M ≤500000。

题解

解1:点分治

分析

这道题目里动态的是需要计入的点,那么我们就不能够一次分治将其求出来了。应用动态点分治的思想,我们可以先把点分树搞出来,然后在每一个重心统计子树信息。具体而言,我们这里要统计的是1.当前重心的所有儿子到重心的距离的集合;2.子树重心1信息最大值的集合;3.全局2信息中最大和次大值之和的集合,答案即是3集合的最大值。为了便于求最大值,我们可以考虑使用STL multiset或者堆来统计这些信息。每一次更新节点信息,我们要将其产生的影响向点分树中的父亲更新。具体而言,假如我们要关掉一个节点的灯,我们要向该节点1集合中插入0,向该节点的点分树祖先1集合插入该节点到祖先的距离,以儿子的1集合值更新祖先2集合值,以涉及的这条链上的点更新3集合的值。
具体的实现可以看代码,有部分注释,但是会有些绕。可以通过手动模拟一遍强行理解。
吐槽:洛谷这个题时限给1s谁过得去嘛!不过好像有人用SPOJ-QTREE4的代码过去了。

代码

下面的代码参考了hzwer的写法,因为自己并不知道怎么写,感谢hzwer学长。

// Code by KSkun, 2018/3
#include <cstdio>

#include <queue>

inline int max(int a, int b) {
    return a > b ? a : b;
} 

inline int min(int a, int b) {
    return a < b ? a : b;
} 

inline void swap(int &a, int &b) {
    register int t = a;
    a = b;
    b = t;
}

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline int readint() {
    register int res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool isop(char c) {
    return c == 'G' || c == 'C';
}

inline char readop() {
    register char c;
    while(!isop(c = fgc()));
    return c;
}

const int MAXN = 300005, INF = 1e9;

int n, m, ut, vt, xt, rt;
char op;
int siz[MAXN], dep[MAXN], msz[MAXN], sum, dfn[MAXN], clk, fa[MAXN], mn[MAXN][20], log2[MAXN], bin[20], tot;
bool vis[MAXN], status[MAXN];

struct Edge {
    int to, nxt;
} gra[MAXN << 1];
int head[MAXN], etot;

inline void addedge(int u, int v) {
    etot++;
    gra[etot].to = v;
    gra[etot].nxt = head[u];
    head[u] = etot;
}

struct Heap {
    std::priority_queue<int> heap, del;
    inline void push(int x) {
        heap.push(x);
    }
    inline void erase(int x) {
        del.push(x);
    }
    inline void pop() {
        while(del.size() && heap.top() == del.top()) heap.pop(), del.pop();
        heap.pop();
    }
    inline int top() {
        while(del.size() && heap.top() == del.top()) heap.pop(), del.pop();
        if(!heap.size()) return 0;
        return heap.top();
    }
    inline int size() {
        return heap.size() - del.size();
    }
    inline int secondTop() {
        if(size() < 2) return 0;
        int t = top(); pop();
        int t1 = top(); push(t);
        return t1;
    }
} A, B[MAXN], C[MAXN];

// 这里计算DFS序便于RMQ,以及处理每个点的深度,用根将子树序列夹起来可以实现查询最小值就能找到LCA的功能
inline void dfs(int u, int fa) {
    dfn[u] = ++clk;
    mn[dfn[u]][0] = dep[u];
    for(register int i = head[u]; i; i = gra[i].nxt) {
        register int v = gra[i].to;
        if(v == fa) continue;
        dep[v] = dep[u] + 1;
        dfs(v, u);
        mn[++clk][0] = dep[u];
    }
}

// 找重心
inline void getrt(int u, int fa) {
    siz[u] = 1;
    msz[u] = 0;
    for(register int i = head[u]; i; i = gra[i].nxt) {
        register int v = gra[i].to;
        if(v == fa || vis[v]) continue;
        getrt(v, u);
        siz[u] += siz[v];
        msz[u] = max(msz[u], siz[v]);
    }
    msz[u] = max(msz[u], sum - siz[u]);
    if(msz[u] < msz[rt]) rt = u;
}

// 点分治的DFS,用于构建点分树
inline void dac(int u, int f) {
    fa[u] = f;
    vis[u] = true;
    for(register int i = head[u]; i; i = gra[i].nxt) {
        register int v = gra[i].to;
        if(vis[v]) continue;
        sum = siz[v];
        rt = 0;
        getrt(v, u);
        dac(rt, u);
    }
}

// DFS序上的倍增RMQ
inline int rmq(int u, int v) {
    u = dfn[u]; v = dfn[v];
    if(u > v) swap(u, v);
    register int t = log2[v - u + 1];
    return min(mn[u][t], mn[v - bin[t] + 1][t]);
}

// 求两点间距离,基于上面的RMQ原理
inline int dis(int u, int v) {
    return dep[u] + dep[v] - 2 * rmq(u, v);
}

// 关灯时要做的事情
inline void off(int u, int v) {
    if(u == v) {
        B[u].push(0);
        if(B[u].size() == 2) A.push(B[u].top());
    }
    if(!fa[u]) return;
    register int f = fa[u], d = dis(f, v), ct = C[u].top();
    C[u].push(d);
    if(d > ct) {
        register int mx = B[f].top() + B[f].secondTop(), size = B[f].size();
        if(ct) B[f].erase(ct);
        B[f].push(d);
        register int nmx = B[f].top() + B[f].secondTop();
        if(nmx > mx) {
            if(size >= 2) A.erase(mx);
            if(B[f].size() >= 2) A.push(nmx);
        }
    }
    off(f, v);
}

// 开灯时要做的事情
inline void on(int u, int v) {
    if(u == v) {
        if(B[u].size() == 2) A.erase(B[u].top());
        B[u].erase(0);
    }
    if(!fa[u]) return;
    register int f = fa[u], d = dis(f, v), ct = C[u].top();
    C[u].erase(d);
        // 如果开灯影响到了2或3集合
    if(d == ct) {
        register int mx = B[f].top() + B[f].secondTop(), size = B[f].size();
        B[f].erase(d);
        if(C[u].top()) B[f].push(C[u].top());
        register int nmx = B[f].top() + B[f].secondTop();
        if(nmx < mx) {
            if(size >= 2) A.erase(mx);
            if(B[f].size() >= 2) A.push(nmx);
        }
    }
    on(f, v);
}

int main() {
        // 把2的若干次幂和log2的值预处理出来,减小常数
    bin[0] = 1;
    for(register int i = 1; i < 20; i++) bin[i] = bin[i - 1] << 1;
    log2[0] = -1;
    for(register int i = 1; i <= 200000; i++) log2[i] = log2[i >> 1] + 1;
    n = readint();
    for(register int i = 1; i < n; i++) {
        ut = readint(); vt = readint();
        addedge(ut, vt);
        addedge(vt, ut);
    }
    dfs(1, 0);
        // 倍增RMQ
    for(register int k = 1; k <= log2[clk]; k++) {
        for(register int i = 1; i <= clk; i++) {
            mn[i][k] = min(mn[i][k - 1], mn[i + bin[k - 1]][k - 1]);
        }
    }
    rt = 0;
    msz[0] = INF;
    sum = n;
    getrt(1, 0);
    dac(rt, 0);
    for(register int i = 1; i <= n; i++) C[i].push(0);
    for(register int i = 1; i <= n; i++) off(i, i), tot++; // 最初全部是关着灯的,给123集合设置初值
    m = readint();
    while(m--) {
        op = readop();
        if(op == 'G') {
            if(tot <= 1) printf("%d\n", tot - 1);
            else printf("%d\n", A.top());
        }
        if(op == 'C') {
            xt = readint();
            if(status[xt]) {
                off(xt, xt);
                tot--;
            } else {
                on(xt, xt);
                tot++;
            }
            status[xt] ^= 1;
        }
    }
    return 0;
}

解2:边分治

分析

类似QTREE4(题解:[SPOJ-QTREE4]Query on a tree IV 题解 | KSkun’s Blog),利用堆把每条中心边两端的dis统计出来,然后每个点记录它影响哪些中心边的答案。每个边计算最优的时候还要计算递归子结构的最优,最后询问的时候就可以不用单独查一次了。每次更新答案的时候要按照记录的倒序更新,因为倒序才是从底到顶的顺序。

代码

(其实是改的QTREE4的代码)

// Code by KSkun, 2018/3
#include <cstdio>
#include <cstring>

#include <vector>
#include <algorithm>
#include <queue>

typedef long long LL;

inline char fgc() {
    static char buf[100000], *p1 = buf, *p2 = buf;
    return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1++;
}

inline LL readint() {
    register LL res = 0, neg = 1;
    register char c = fgc();
    while(c < '0' || c > '9') {
        if(c == '-') neg = -1;
        c = fgc();
    }
    while(c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = fgc();
    }
    return res * neg;
}

inline bool isop(char c) {
    return c == 'G' || c == 'C';
}

inline char readop() {
    char c;
    while(!isop(c = fgc()));
    return c;
}

const int MAXN = 1000005, INF = 2e9;

int n, q, col[MAXN], ans;

struct Edge {
    int to, w, nxt;
} gra[MAXN << 1], grao[MAXN << 1];
int head[MAXN], heado[MAXN], ecnt, ecnto;

inline void addedge(int u, int v, int w) {
    gra[ecnt] = Edge {v, w, head[u]}; head[u] = ecnt++;
}

inline void addedgeo(int u, int v, int w) {
    grao[ecnto] = Edge {v, w, heado[u]}; heado[u] = ecnto++;
}

inline void rebuild(int u, int fa) {
    int ff = 0;
    for(int i = heado[u]; ~i; i = grao[i].nxt) {
        int v = grao[i].to, w = grao[i].w;
        if(v == fa) continue;
        if(!ff) {
            addedge(u, v, w);
            addedge(v, u, w);
            ff = u;
        } else {
            int k = ++n;
            col[k] = 1;
            addedge(ff, k, 0);
            addedge(k, ff, 0);
            addedge(k, v, w);
            addedge(v, k, w);
            ff = k;
        }
        rebuild(v, u);
    }
}

bool del[MAXN << 1];
int ct, ctsiz, sum;
int siz[MAXN], msz[MAXN];

inline void calsiz(int u, int fa) {
    siz[u] = 1;
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(del[i >> 1] || v == fa) continue;
        calsiz(v, u);
        siz[u] += siz[v];
    }
}

inline void findct(int u, int fa) {
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to;
        if(del[i >> 1] || v == fa) continue;
        findct(v, u);
        int vsiz = std::max(siz[v], sum - siz[v]);
        if(vsiz < ctsiz) {
            ct = i;
            ctsiz = vsiz;
        }
    }
}

struct DisData {
    int u, d;
    inline bool operator<(const DisData &rhs) const {
        return d < rhs.d;
    }
};

std::priority_queue<DisData> s[MAXN][2];
int cnt;

struct NodeData {
    int bel, side, dis;
};
std::vector<NodeData> ndata[MAXN];

inline void caldis(int u, int fa, int d, int t, int l) {
    if(!col[u]) {
        s[t][l].push(DisData {u, d}); ndata[u].push_back(NodeData {t, l, d});
    }
    for(int i = head[u]; ~i; i = gra[i].nxt) {
        int v = gra[i].to, w = gra[i].w;
        if(del[i >> 1] || v == fa) continue;
        caldis(v, u, d + w, t, l);
    }
}

int mx[MAXN], lch[MAXN], rch[MAXN], ctw[MAXN];

inline void update(int p) {
    while(!s[p][0].empty() && col[s[p][0].top().u]) s[p][0].pop();
    while(!s[p][1].empty() && col[s[p][1].top().u]) s[p][1].pop();
    if(s[p][0].empty() || s[p][1].empty()) mx[p] = 0;
    else mx[p] = s[p][0].top().d + ctw[p] + s[p][1].top().d;
    if(lch[p]) mx[p] = std::max(mx[p], mx[lch[p]]);
    if(rch[p]) mx[p] = std::max(mx[p], mx[rch[p]]);
}

inline int divide(int u) {
    calsiz(u, 0);
    ct = -1; ctsiz = INF; sum = siz[u]; findct(u, 0);
    if(ct == -1) return 0;
    int x = gra[ct].to, y = gra[ct ^ 1].to;
    del[ct >> 1] = true;
    int t = ++cnt;
    ctw[t] = gra[ct].w;
    caldis(x, 0, 0, t, 0); caldis(y, 0, 0, t, 1);
    lch[t] = divide(x); rch[t] = divide(y); 
    update(t);
    return t;
}

inline void setwhite(int u) {
    for(int i = ndata[u].size() - 1; i >= 0; i--) {
        NodeData d = ndata[u][i];
        s[d.bel][d.side].push(DisData {u, d.dis});
        update(d.bel);
    }
}

inline void setblack(int u) {
    for(int i = ndata[u].size() - 1; i >= 0; i--) {
        NodeData d = ndata[u][i];
        update(d.bel);
    }
}

int ut, vt, wt;
char op;

int main() {
    memset(head, -1, sizeof(head));
    memset(heado, -1, sizeof(heado));
    n = readint();
    int white = n;
    for(int i = 1; i < n; i++) {
        ut = readint(); vt = readint();
        addedgeo(ut, vt, 1);
        addedgeo(vt, ut, 1);
    }
    rebuild(1, 0);
    divide(1);
    q = readint();
    while(q--) {
        op = readop();
        if(op == 'G') {
            if(!white) {
                puts("-1");
            } else if(white == 1) {
                puts("0");
            } else {
                printf("%d\n", mx[1]);
            }
        } else {
            ut = readint(); 
            col[ut] ^= 1;
            if(col[ut]) {
                setblack(ut);
                white--;
            } else {
                setwhite(ut);
                white++;
            }
        }
    }
    return 0;
}