题目描述

捉迷藏 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。

样例输入

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

样例输出

4
3
3
4


题解

动态点分治 +堆

动态点分治:将点分治的上一层重心与下一层连边,可以得到一棵新树(点分树)。由于每次都是找重心,所以树高不超过$\log$,就可以使用各种数据结构维护各种子树信息。

考虑如果本题是静态的,只有一次查询该怎么做:求出以每个点为根的最长路径,即求 $|$所有节点的 $|$子节点的 $|$子树中的节点到父亲节点的最大值$|$ 的最大值和次大值的和$|$ 的最大值$|$。($|$为断句方法= =)

形象一点,求每个点的子树中的所有节点到父亲节点的距离的最大值$p1$;每个点求出它所有子节点的$p1$以及当前节点状态(存在则为0)中的最大的和次大的,加起来得到$p2$;所有节点的$p2$的最大值就是$p3$。

考虑带修改,多次查询:首先由于有修改,所以树高必须要有保证,所以选择动态树分治的点分树结构。

那么需要是用数据结构,支持查询最大值和次大值,使用3种堆:

$s1[]$:维护一个子树中所有节点到当前点的父亲节点的距离;

$s2[]$:维护一个点的所有子分治节点(点分树中子节点)的$s1$中的最大值,如果当前节点可用,则需要再增加一个$0$;

$s3$:维护所有节点的$s2$的最大值与次大值(如果存在)之和。

每次$s3$的最大值就是答案。

于是就可以自底向上修改路径上的$s1$和$s2$,并修改$s3$。具体实现较为复杂:需要消除下一级对上一级的影响,所以要先删除上一级,再插入上一级;需要实现可以删除的堆,于是需要维护两个堆,删除时将要删的数加入到辅助堆中,每次取堆顶时如果两堆堆顶相同则都弹出。

并且需要维护欧拉遍历序并使用RMQLCA支持$O(1)$查询LCA以保证时间复杂度。

总时间复杂度为$O(n\log^2n)$,空间复杂度为$O(n\log n)$。

#include <queue>
#include <cstdio>
#define N 100010
using namespace std;
struct heap
{
priority_queue<int> A , B;
void push(int x) {A.push(x);}
void del(int x) {B.push(x);}
int top()
{
while(!B.empty() && A.top() == B.top()) A.pop() , B.pop();
return A.top();
}
int sum()
{
int a = top(); A.pop();
int b = top(); push(a);
return a + b;
}
int size() {return A.size() - B.size();}
}s1[N] , s2[N] , s3;
int head[N] , to[N << 1] , next[N << 1] , cnt , vis[N] , deep[N] , pos[N] , md[20][N << 1];
int si[N] , mx[N] , sum , root , log[N << 1] , tot , fa[N] , val[N];
char str[5];
void insert(heap &s) {if(s.size() >= 2) s3.push(s.sum());}
void erase(heap &s) {if(s.size() >= 2) s3.del(s.sum());}
void add(int x , int y)
{
to[++cnt] = y , next[cnt] = head[x] , head[x] = cnt;
}
void dfs(int x , int fa)
{
int i;
md[0][++tot] = deep[x] , pos[x] = tot;
for(i = head[x] ; i ; i = next[i])
if(to[i] != fa)
deep[to[i]] = deep[x] + 1 , dfs(to[i] , x) , md[0][++tot] = deep[x];
}
int lca(int x , int y)
{
x = pos[x] , y = pos[y];
if(x > y) swap(x , y);
int k = log[y - x + 1];
return min(md[k][x] , md[k][y - (1 << k) + 1]);
}
void getroot(int x , int fa)
{
int i;
si[x] = 1 , mx[x] = 0;
for(i = head[x] ; i ; i = next[i])
if(!vis[to[i]] && to[i] != fa)
getroot(to[i] , x) , si[x] += si[to[i]] , mx[x] = max(mx[x] , si[to[i]]);
mx[x] = max(mx[x] , sum - si[x]);
if(mx[x] < mx[root]) root = x;
}
void solve(int x)
{
int i;
vis[x] = 1;
for(i = head[x] ; i ; i = next[i])
if(!vis[to[i]])
sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , fa[root] = x , solve(root);
}
void join(int x)
{
erase(s2[x]) , s2[x].push(0) , insert(s2[x]);
int t;
for(t = x ; fa[t] ; t = fa[t])
{
erase(s2[fa[t]]);
if(s1[t].size()) s2[fa[t]].del(s1[t].top());
s1[t].push(deep[fa[t]] + deep[x] - 2 * lca(fa[t] , x)) , s2[fa[t]].push(s1[t].top());
insert(s2[fa[t]]);
}
}
void remove(int x)
{
erase(s2[x]) , s2[x].del(0) , insert(s2[x]);
int t;
for(t = x ; fa[t] ; t = fa[t])
{
erase(s2[fa[t]]);
s2[fa[t]].del(s1[t].top()) , s1[t].del(deep[fa[t]] + deep[x] - 2 * lca(fa[t] , x));
if(s1[t].size()) s2[fa[t]].push(s1[t].top());
insert(s2[fa[t]]);
}
}
int main()
{
int n , m , i , j , x , y , num;
scanf("%d" , &n) , num = n;
for(i = 1 ; i < n ; i ++ ) scanf("%d%d" , &x , &y) , add(x , y) , add(y , x);
dfs(1 , 0);
for(i = 2 ; i <= tot ; i ++ ) log[i] = log[i >> 1] + 1;
for(i = 1 ; (1 << i) <= tot ; i ++ )
for(j = 1 ; j <= tot - (1 << i) + 1 ; j ++ )
md[i][j] = min(md[i - 1][j] , md[i - 1][j + (1 << (i - 1))]);
mx[0] = 1 << 30 , sum = n , getroot(1 , 0) , solve(root);
for(i = 1 ; i <= n ; i ++ ) val[i] = 1 , join(i);
scanf("%d" , &m);
while(m -- )
{
scanf("%s" , str);
if(str[0] == 'G')
{
if(num >= 2) printf("%d\n" , s3.top());
else printf("%d\n" , num - 1);
}
else
{
scanf("%d" , &x);
if(val[x]) num -- , val[x] = 0 , remove(x);
else num ++ , val[x] = 1 , join(x);
}
}
return 0;
}

【bzoj1095】[ZJOI2007]Hide 捉迷藏 动态点分治+堆的更多相关文章

  1. BZOJ1095 [ZJOI2007]Hide 捉迷藏 动态点分治 堆

    原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ1095.html 题目传送门 - BZOJ1095 题意 有 N 个点,每一个点是黑色或者白色,一开始所 ...

  2. 【BZOJ1095】[ZJOI2007]Hide 捉迷藏 动态树分治+堆

    [BZOJ1095][ZJOI2007]Hide 捉迷藏 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉 ...

  3. bzoj1095: [ZJOI2007]Hide 捉迷藏 动态点分治学习

    好迷啊...感觉动态点分治就是个玄学,蜜汁把树的深度缩到logn (静态)点分治大概是递归的时候分类讨论: 1.答案经过当前点,暴力(雾)算 2.答案不经过当前点,继续递归 由于原树可以长的奇形怪状( ...

  4. BZOJ1095:[ZJOI2007]Hide 捉迷藏(动态点分治)

    Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩 捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条 ...

  5. BZOJ 1095: [ZJOI2007]Hide 捉迷藏 动态点分治+堆

    写了7k多,可以说是一己之力切掉了这道毒瘤题~ 开 $3$ 种堆,分别维护每个子树最大深度,以及每个节点在点分树中对父亲的贡献,和全局的最优解. 由于需要支持堆的删除,所以写起来特别恶心+麻烦. 细节 ...

  6. 洛谷.4115.Qtree4/BZOJ.1095.[ZJOI2007]Hide捉迷藏(动态点分治 Heap)

    题目链接 洛谷 SPOJ BZOJ1095(简化版) 将每次Solve的重心root连起来,会形成一个深度为logn的树,就叫它点分树吧.. 我们对每个root维护两个东西: 它管辖的子树中所有白点到 ...

  7. bzoj 1095 Hide 捉迷藏 - 动态点分治 -堆

    Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双 ...

  8. BZOJ 1095 [ZJOI2007]Hide 捉迷藏 ——动态点分治

    [题目分析] 这题好基啊. 先把分治树搞出来.然后每个节点两个堆. 第一个堆保存这个块里的所有点(即分治树中的所有儿子)到分治树上的父亲的距离. 第二个堆保存分治树子树中所有儿子第一个堆的最大值. 建 ...

  9. BZOJ 1095: [ZJOI2007]Hide 捉迷藏(动态点分治)

    传送门 解题思路 点分树其实就是在点分治的基础上,把重心连起来.这样树高是\(log\)的,可以套用数据结构进行操作.这道题是求最远距离,所以每个点维护两个堆,分别表示所管辖的子树的最远距离和到父节点 ...

随机推荐

  1. POJ 3126 Prime Path(筛法,双向搜索)

    题意:一个4位的素数每次变动一个数位,中间过程也要上素数,问变成另一个的最小步数. 线性筛一遍以后bfs就好.我写的双向,其实没有必要. #include<cstdio> #include ...

  2. 【BZOJ1036】[ZJOI2008] 树的统计Count(一道可怕的模板题:树剖+线段树)

    点此看题面 题解 这真的只是一道模板题:一个树链剖分套上一个线段树(令我窒息的组合). 既然是模板题,那就直接上代码吧. 代码 #include<bits/stdc++.h> #defin ...

  3. 如果int x=20, y=5,则语句System.out.println(x+y +""+(x+y)+y); 的输出结果是()

    答案是25255 小括号优先级高,所以先算小括号内的x+y=25 然后再算前面的x+y=25 但是中间有个空的字符串,java会把这个空字符串后面的都当成字符串看待,所以结果是25255

  4. JMeter接口压力测试课程入门到高级实战

    章节一压力测试课程介绍 1.2018年亿级流量压测系列之Jmeter4.0课程介绍和效果演示 简介: 讲解课程安排,使用的Jmeter版本 讲课风格:涉及的组件,操作配置多,不会一次性讲解,会先讲部分 ...

  5. C# DateTime的使用

    获得当前系统时间: DateTime dt = DateTime.Now; Environment.TickCount可以得到“系统启动到现在”的毫秒值 DateTime now = DateTime ...

  6. Oracle问题分析采集数据的方法

    1.背景: 运维人员或多或少都会遇到分析问题.分析故障的时候,往往在碰到一些棘手的问题事,我们都会往更深层次的专家进行求助.不管是二线专家还是Oracle全球服务工程师(后文称GCS工程师),往往都会 ...

  7. MySQL解决中文编码问题

    转载组员博客 地址:MySQL解决中文编码问题

  8. vue 判断是否登录,未登录跳转到登录页

    网页一进入判断是否登录,未登录跳转到登录页面 router.js export default new Router({ routes: [ { path: '/', name: 'HelloWorl ...

  9. 如何在 Linux 中配置基于密钥认证的 SSH

    什么是基于 SSH 密钥的认证? 众所周知,Secure Shell,又称 SSH,是允许你通过无安全网络(例如 Internet)和远程系统之间安全访问/通信的加密网络协议.无论何时使用 SSH 在 ...

  10. Missian指南三:创建一个Missian服务器(使用spring)

    在使用Missian时,spring是可选的,但是作者本人强烈推荐和Spring配合使用.Spring是一个伟大的项目,并且它不会对程序在运行时的效率带来任何损耗. Missian在服务器端依赖与Mi ...