BZOJ 1095 ZJOI 2007 Hide 捉迷藏 动态点分治

时间:2020-12-31 09:28:46

动态点分治?
就是内存卡的很紧?用了154MB。。。
第一次写参考了PoPoQQQ大爷的代码。

而做到改查就需要依赖数据结构,本题询问最远距离,即对于某个根节点的两子树的最远距离,如果我们能同时维护子树内离根最远的黑点的距离和根的两个子树且最远距离在子树间最大,即最大和次大值,问题就很好办了。可以为每个点造2个堆 h1 h2 ,分别维护子树内各点到根的距离和子树中 h1 的最大值。
那么最终答案就是每个点的 h2 的最大值和次大值的和的最大值。
对于动态修改,如果是关灯,对于本点的 h2 多加一个0,然后由于本点的该边只会影响其祖先,所以维护其到根的路径上的点的 h1 ,即添加该点到本点的距离。开灯相反。
在维护堆的时候需要同时维护依赖其维护值的其他堆以保证答案的实时性。

然后这么维护的话树的形态不好会导致单次修改的复杂度退化为 O(nlogn) ,由于我们不在意确切的路径怎么走,只关心某个点和其他黑点间的距离,因此预处理出各点间距,点间的连边便对我们要维护的值无影响了。
原因是我们始终维护的是【子树内的点】,而不是【边】,也就是说,我们划定了一些点,其数据被其他的点所维护,。然后子树内的点是什么,不影响最终答案,不妨特别地构造一棵树,使树高为 O(logn) ,这样复杂度就限定为 O(log2n) ,而点分治完全符合我们的需求,在利用的重心间建立连接的树的形态下预处理出所有堆的值,保证复杂度。

最终复杂度 O(nlog2n)

#include <queue>
#include <cstdio>
#include <algorithm>
#define FOR(i,j,k) for(i=j;i<=k;++i)
const int N = 100005, M = N * 2;
using namespace std;

struct Heap {
    priority_queue<int> h, d;
    void add(int x) { h.push(x); }
    void del(int x) { d.push(x); }
    void chg(int x, int f) { if (f) add(x); else del(x); }
    void fix() { while (d.size() && h.top() == d.top()) h.pop(), d.pop(); }
    void pop() { fix(); h.pop(); }
    int fir() { fix(); return h.top(); }
    int sec() { int a, b; a = fir(); pop(); b = fir(); add(a); return b; }
    int sz() { return h.size() - d.size(); }
} s1[N], s2[N], ans;

int vis[M], p[M], v[M], h[N], fa[N], cnt = 1, node, rt;
int lb[M], dep[N], g[N], sz[N], pos[N], r[M][18], id, light[N];
void add(int a, int b) {
    p[++cnt] = h[a]; v[cnt] = b; h[a] = cnt;
}
void root(int x, int fa) {
    sz[x] = 1; g[x] = 0;
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa && !vis[i]) {
            root(v[i], x);
            sz[x] += sz[v[i]];
            g[x] = max(g[x], sz[v[i]]);
        }
    g[x] = max(g[x], node - sz[x]);
    if (g[x] < g[rt]) rt = x;
}
int get_root(int x, int fa, int sz) {
    rt = 0; node = sz; g[0] = 2147483647;
    root(x, fa); return rt;
}
void dfs_seq(int x, int fa, int dep, Heap &s) {
    s.add(dep);
    for (int i = h[x]; i; i = p[i])
        if (!vis[i] && v[i] != fa)
            dfs_seq(v[i], x, dep + 1, s);
}
void add(Heap &s) { if(s.sz() >= 2) ans.add(s.fir() + s.sec()); }
void del(Heap &s) { if(s.sz() >= 2) ans.del(s.fir() + s.sec()); }
void work(int x) {
    s2[x].add(0);
    for (int i = h[x]; i; i = p[i])
        if (!vis[i]) {
            vis[i] = vis[i ^ 1] = true;
            Heap s;
            dfs_seq(v[i], 0, 1, s);
            int cg = get_root(v[i], x, sz[v[i]]);
            fa[cg] = x; s1[cg] = s;
            s2[x].add(s1[cg].fir());
            work(cg);
        }
    add(s2[x]);
}
void dfs_seq(int x,int fa) {
    r[++id][0] = dep[x] = dep[fa] + 1; pos[x] = id;
    for (int i = h[x]; i; i = p[i])
        if (v[i] != fa) {
            dfs_seq(v[i], x);
            r[++id][0] = dep[x];
        }
}
void init_rmq(int s) {
    int i, j;
    FOR(i,2,s) lb[i] = lb[i >> 1] + 1;
    FOR(j,1,lb[s]) FOR(i,1,(s+1-(1<<j)))
        r[i][j] = min(r[i][j - 1], r[i + (1 << j - 1)][j - 1]);
}
int rmq(int a, int b) {
    a = pos[a]; b = pos[b];
    if (a > b) swap(a, b);
    int t = lb[b - a + 1];
    return min(r[a][t], r[b - (1 << t) + 1][t]);
}
int dis(int a, int b) {
    return dep[a] + dep[b] - 2 * rmq(a, b);
}
void turn(int x, int flag) {
    del(s2[x]); s2[x].chg(0, flag); add(s2[x]);
    for (int i = x; fa[i]; i = fa[i]) {
        del(s2[fa[i]]);
        if (s1[i].sz()) s2[fa[i]].del(s1[i].fir());
        s1[i].chg(dis(fa[i],x), flag);
        if (s1[i].sz()) s2[fa[i]].add(s1[i].fir());
        add(s2[fa[i]]);
    }
}
int main() {
    int i, j, x, y, n, m, dark; char p[2];
    scanf("%d", &n); dark = n;
    FOR(i,2,n) scanf("%d%d", &x, &y), add(x, y), add(y, x);
    work(get_root(1, 0, n));
    dfs_seq(1,0);
    init_rmq(id);
    scanf("%d", &m);
    FOR(i,1,m) {
        scanf("%s", p);
        if (p[0] == 'G')
            if (dark <= 1) printf("%d\n", dark - 1);
            else printf("%d\n", ans.fir());
        else {
            scanf("%d",&x);
            turn(x, light[x]);
            light[x] ? ++dark : --dark;
            light[x] = !light[x];
        }
    }
    return 0;
}

1095: [ZJOI2007]Hide 捉迷藏

Description

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

Input

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

Output

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

Sample Input

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

Sample Output

4
3
3
4

HINT

对于100%的数据, N ≤100000, M ≤500000。