bzoj1095 Hide 捉迷藏 动态点分治+堆 (附动态点分治详解)

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

1095: [ZJOI2007]Hide 捉迷藏

Time Limit: 40 Sec   Memory Limit: 256 MB
Submit: 3978   Solved: 1674
[ Submit][ Status][ Discuss]

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。


题目大意:给一颗树,节点分黑白,开始全黑,给两个操作,要么把一个节点黑白变化,要么询问树上最远黑点距离

呼呼,终于写(chao)完了这道动态点分治的题目。

首先不懂点分治的戳这里(记得把题目也写写,写完再来看这道)

对于这道题我们考虑不带修改的情况,及直接询问树上最远黑点的距离,显然是一个裸的树dp,只要用一个数组f记录每个子树中黑点到子树根最远距离,然后更新答案的时候用最大的加上次大的就可以很简单地解决这道题。显然,这个树dp带有很强的点分治的影子:考虑了每个子树对根的影响。

此时我们考虑修改。如果暴力修改肯定是不行地。我们考虑修改的节点对答案的影响。发现这个节点仅仅会影响到其到跟路径上的节点的答案,不会影响到其他节点的答案。因此,我们每次修改只需要修改这个节点到根节点的答案就可以了。

然而,如果树是一条链,那么我们的算法显然会退化为O(nmk)其中n是点数,m是修改次数,k是修改一个节点信息的复杂度。

因此我们希望有一种算法,使得每次修改的时间复杂度为logn,也就是说,我们希望通过某种手段,使得每个节点修改后只需要修改logn个节点。像这样,对于一颗无根树进行修改和询问,且可以将问题划分到每棵子树上的这类问题,我们有一种算法,叫做动态点分治。

其实,它和点分治一样,只不过把树dp的操作顺序稍微修改了一下。

我们在原树的基础之上,建立出一颗新的“分治树”,这棵分治树的特点是它满足点分治时的操作顺序。也就是说,这颗分治树上每个节点都是其子树上所有节点在原树上形成的连通块的重心。这样显然满足,这颗分治树的树高不会超过logn。那么我们在进行树dp或树上操作的时候,建出分治树并维护分治树上每个节点的信息。修改的时候从待修改的节点往根向上更新,就可以保证修改的logn复杂度。

那么,如何建立这颗分治树呢,其实和点分治几乎一模一样,只不过在点分治的时候,加上一句prt[u] = pa,就可以把这可分治树方便地建立出来,从而在上面进行操作。下面是代码

void get_root(int u, int pa) {
son[u] = 1; f[u] = 0;
for(int i = pre[u]; i;i = e[i].next)
if(e[i].to != pa && !vis[e[i].to]) {
get_root(e[i].to, u);
son[u] += son[e[i].to];
f[u] = max(f[u], son[e[i].to]);
}
f[u] = max(f[u], sums - son[u]);
if(f[u] < f[root]) root = u;
}

void Div(int u, int pa) {
    prt[u] = pa; vis[u] = 1; int pre_sums = sums; 
    for(int i = pre[u]; i; i = e[i].next) 
    if(!vis[e[i].to]) {
        if(son[e[i].to] > son[u]) sums = pre_sums - son[u];
        else sums = son[e[i].to];
        root = 0;
        get_root(e[i].to, 0);
        Div(root, u);
    }
}

是不是不可思议地简单!好了,接下来让我们回到这一题。

记得不带修改时的做法?用一个数组f记录每个子树中黑点到子树根最远距离,然后更新答案的时候用最大的加上次大的。显然这里面包含了三层的最大:

1.      子树中黑点到子树根最大

2.      黑点到到每棵子树最大值加上到父亲距离的最大值

3.      全局每个节点在(2)中的最大值加上次大值

对于这三个东西,我们维护三层的堆即可。为了方便,我们把1稍微改一下,改成子树中所有黑点到子树根父亲的最大值,这样子省去了在更新时还要计算子树和父亲的距离(记得所有子树和父亲的距离要重新计算,因为分治树和原树有一个映射的过程)

关于距离,当然是选择rmq最快

关于堆,由于这个堆还要删除,所以可以使用multiset(STL大法好)。然而hzwer采用了两个priority_queue模拟出了multiset,真是吧STL使用得出神入化,orz,于是我get到了这个新技能。

最后说一下我的错误,当时没有考虑到点分治面对的是一棵无根树,于是随便提了个跟并且限定了父亲,在getroot的时候只带了一个参数,判断的时候强行用父子关系卡,结果当然是错掉了qwq

全-代码:好烦好烦啊

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#include<queue>
#define maxn 100005
#define maxm 200005
using namespace std;
int read()
{
char ch = getchar(); int x = 0, f = 1;
while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
return x * f;
}

struct Heap {
priority_queue<int>A,B;
void push(int x) {A.push(x);}
void erase(int x) {B.push(x);}
void pop() {
while(B.size() && A.top() == B.top())
A.pop(), B.pop();
}
int top() {
pop();
if(!A.size()) return 0;
return A.top();
}
int size() {return A.size() - B.size();}
int stop() {
if(size() < 2) return 0;
int x = top(); A.pop();
int y = top(); push(x);
return y;
}
}A, B[maxn], C[maxn];

int pre[maxn], top;
struct edge
{
int to, next;
void add(int u, int v)
{
to = v; next = pre[u];
pre[u] = top;
}
}e[maxm];
int cnt, n, m, root, size, sums, tot, son[maxn], d[maxn], f[maxn], deep[maxn], pos[maxn];
int mn[maxm][18], bin[20], LOG[maxm], prt[maxn];
bool light[maxn], vis[maxn];

void adds(int u, int v)
{
e[++top].add(u, v);
e[++top].add(v, u);
}

void dfs(int u, int pa) {
deep[u] = deep[pa] + 1;
mn[++tot][0] = deep[u]; pos[u] = tot;
for(int i = pre[u]; i; i = e[i].next)
if(e[i].to != pa)
{
dfs(e[i].to, u);
mn[++tot][0] = deep[u];
}
}

void RMQ_pre() {
for(int j = 1; j <= LOG[tot]; ++j)
for(int i = 1;i + bin[j] - 1 <= tot; ++i)
mn[i][j] = min(mn[i][j - 1], mn[i + bin[j - 1]][j - 1]);
}

int rmq(int u, int v) {
u = pos[u], v = pos[v];
if(u > v) swap(u, v);
int t = LOG[v - u + 1];
return min(mn[u][t], mn[v - bin[t] + 1][t]);
}
int dis(int u, int v) {return deep[u] + deep[v] - (rmq(u, v) << 1);}

void get_root(int u, int pa) {
son[u] = 1; f[u] = 0;
for(int i = pre[u]; i;i = e[i].next)
if(e[i].to != pa && !vis[e[i].to]) {
get_root(e[i].to, u);
son[u] += son[e[i].to];
f[u] = max(f[u], son[e[i].to]);
}
f[u] = max(f[u], sums - son[u]);
if(f[u] < f[root]) root = u;
}

void Div(int u, int pa) {
    prt[u] = pa; vis[u] = 1; int pre_sums = sums; 
    for(int i = pre[u]; i; i = e[i].next) 
    if(!vis[e[i].to]) {
        if(son[e[i].to] > son[u]) sums = pre_sums - son[u];
        else sums = son[e[i].to];
        root = 0;
        get_root(e[i].to, 0);
        Div(root, u);
    }
}

void Turn_off(int u, int v) {
if(u == v) {
B[u].push(0);
if(B[u].size() == 2) A.push(B[u].top());
}
if(!prt[u]) return;
int pa = prt[u], D = dis(pa, v), tmp = C[u].top();
C[u].push(D);
if(D > tmp) {
int mx = B[pa].top() + B[pa].stop(), size = B[pa].size();
if(tmp) B[pa].erase(tmp);
B[pa].push(D);
int now = B[pa].top() + B[pa].stop();
if(now > mx) {
if(size >= 2) A.erase(mx);
if(B[pa].size() >= 2) A.push(now);
}
}
Turn_off(pa, v);
}


void Turn_on(int u, int v) {
if(u == v) {
if(B[u].size() == 2) A.erase(B[u].top());
B[u].erase(0);
}
if(!prt[u]) return;
int pa = prt[u], D = dis(pa, v), tmp = C[u].top();
C[u].erase(D);
if(D == tmp) {
int mx = B[pa].top() + B[pa].stop(), size = B[pa].size();
B[pa].erase(D);
if(C[u].top()) B[pa].push(C[u].top());
int now = B[pa].top() + B[pa].stop();
if(now < mx) {
if(size >= 2) A.erase(mx);
if(B[pa].size() >= 2) A.push(now);
}
}
Turn_on(pa, v);
}

void init() {
bin[0] = 1; for(int i = 1;i < 20; ++i) bin[i] = bin[i - 1] << 1;
LOG[0] = -1; for(int i = 1;i <= 200000; ++i) LOG[i] = LOG[i >> 1] + 1;
cnt = n = read();
for(int i = 1;i < n; ++i) adds(read(), read());
dfs(1, 0); RMQ_pre();
sums = n; root = 0; f[0] = 1000000000;
get_root(1, 0); Div(root, 0);
for(int i = 1;i <= n; ++i) Turn_off(i, i);
}

void solve() {
m = read();
while(m--) {
char ch = getchar();
while(ch != 'C' && ch != 'G') ch = getchar();
if(ch == 'C') {
int i = read();
if(light[i]) Turn_off(i, i), ++cnt;
else Turn_on(i, i), --cnt;
light[i] ^= 1;
}
if(ch == 'G') {
if(cnt <= 1) printf("%d\n", cnt - 1);
else printf("%d\n", A.top());
}
}
}

int main()
{
init();
solve();
return 0;
}