[ZJOI 2007]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
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
3
3
4
HINT
对于100%的数据, N ≤100000, M ≤500000。
题解
如果这道题不修改,显然我们能够想到树形 $DP$ 或者点分来做。
然而对于修改的,显然不能再用静态的做法实现。
这里就使用一种新的思想:动态点分。
它的主要思想就是通过将的重心相连来维护一棵“点分树”,通过“点分树”中各个节点与其儿子间的联系来实现在线修改和查询的目的。
对于这道题:我们需要的就是对于每个重心,找到以这个重心为起点向其子树延伸出的最长链和次长链。
我们可以考虑维护一个堆,来维护断开重心后形成的各个联通块中所有的黑点到重心的距离。
由于最长链和次长链不能来自同一个联通块,我们考虑再维护一个堆来维护以它为重心分割出的各个联通块的保存距离的堆的堆顶。
值得注意的是,重心自己如果是黑点的话是可以连向任意一个子树,所以不妨假设重心自己也是一个独立的联通块,所以一开始第二个堆中需要 $push(0)$ 。
这第二个堆中的最大值和次大值(如果有的话)就是经过这个重心的最长路径。
我们考虑再维护一个堆,来存第二个堆的堆顶,这样的话,第三个堆的堆顶就是询问的答案。
对于修改操作,显然我们要将其被管辖的所有重心都要进行修改,这时我们只要暴力在“分治树”上跳就行了,因为“分治树”的深度不会大于 $log_2 n$ 层的。
点分复杂度是 $log_2 n$ 的,堆的操作也是 $log_2 n$ 的,总复杂度为 $O(n log_2^2 n)$ 。
//It is made by Awson on 2018.1.6
#include <set>
#include <map>
#include <cmath>
#include <ctime>
#include <queue>
#include <stack>
#include <cstdio>
#include <string>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define RE register
#define lowbit(x) ((x)&(-(x)))
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
#define Swap(a, b) ((a) ^= (b), (b) ^= (a), (a) ^= (b))
using namespace std;
const int N = ;
const int M = ;
const int INF = ~0u>>; int n, m, u, v, fa[N+], light[N+];
char ch[];
struct tt {
int to, next;
}edge[(N<<)+];
int path[N+], top;
void add(int u, int v) {
edge[++top].to = v;
edge[top].next = path[u];
path[u] = top;
}
namespace LCA {
int bin[], lim, dep[N+], fa[N+][];
void dfs(int o, int depth, int father) {
fa[o][] = father, dep[o] = depth;
for (int i = path[o]; i; i = edge[i].next)
if (edge[i].to != father) dfs(edge[i].to, depth+, o);
}
int query(int x, int y) {
if (dep[x] < dep[y]) Swap(x, y);
for (int i = lim; i >= ; i--) if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
if (x == y) return x;
for (int i = lim; i >= ; i--) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][];
}
int dist(int x, int y) {return dep[x]+dep[y]-(dep[query(x, y)]<<); }
void main() {
lim = log(n)/log(), bin[] = ; for (int i = ; i <= ; i++) bin[i] = bin[i-]<<;
dfs(, , );
for (int t = ; t <= lim; t++) for (int i = ; i <= n; i++) fa[i][t] = fa[fa[i][t-]][t-];
}
}
struct heap {
priority_queue<int>q, p;
void pop() {
while (!q.empty() && !p.empty() && q.top() == p.top()) q.pop(), p.pop();
q.pop();
}
void erase(int val) {p.push(val); }
int top() {
while (!q.empty() && !p.empty() && q.top() == p.top()) q.pop(), p.pop();
return q.top();
}
int sec() {
while (!q.empty() && !p.empty() && q.top() == p.top()) q.pop(), p.pop();
int tmp = q.top(); q.pop();
while (!q.empty() && !p.empty() && q.top() == p.top()) q.pop(), p.pop();
int val = q.top();
q.push(tmp); return val;
}
int size() {return q.size()-p.size(); }
void push(int val) {q.push(val); }
}q1[N+], q2[N+], q3;
namespace Point_divide {
int size[N+], mx[N+], vis[N+], root, minsize;
void get_size(int o, int fa) {
size[o] = , mx[o] = ;
for (int i = path[o]; i; i = edge[i].next)
if (edge[i].to != fa && !vis[edge[i].to]) {
get_size(edge[i].to, o);
size[o] += size[edge[i].to];
if (size[edge[i].to] > mx[o]) mx[o] = size[edge[i].to];
}
}
void get_root(int o, int pa, int fa) {
mx[o] = Max(mx[o], size[pa]-size[o]);
if (mx[o] < minsize) minsize = mx[o], root = o;
for (int i = path[o]; i; i = edge[i].next)
if (edge[i].to != fa && !vis[edge[i].to]) get_root(edge[i].to, pa, o);
}
void get_dist(int o, int pa, int fa) {
q1[root].push(LCA::dist(pa, o));
for (int i = path[o]; i; i = edge[i].next)
if (edge[i].to != fa && !vis[edge[i].to]) get_dist(edge[i].to, pa, o);
}
void work(int o, int pa) {
minsize = INF; get_size(o, ), get_root(o, o, );
fa[root] = pa, vis[root] = ; q2[root].push(), q1[root].push(LCA::dist(pa, root));
for (int i = path[root]; i; i = edge[i].next)
if (!vis[edge[i].to]) get_dist(edge[i].to, pa, root);
q2[pa].push(q1[root].top()); int rt = root;
for (int i = path[root]; i; i = edge[i].next)
if (!vis[edge[i].to]) work(edge[i].to, rt);
if (q2[rt].size() >= ) q3.push(q2[rt].top()+q2[rt].sec());
}
void main() {work(, ); }
} void turnon(int o) {
if (q2[o].size() >= ) q3.erase(q2[o].top()+q2[o].sec());
q2[o].erase();
if (q2[o].size() >= ) q3.push(q2[o].top()+q2[o].sec());
for (int x = o; fa[x]; x = fa[x]) {
if (q2[fa[x]].size() >= ) q3.erase(q2[fa[x]].top()+q2[fa[x]].sec());
q2[fa[x]].erase(q1[x].top());
q1[x].erase(LCA::dist(o, fa[x]));
if (q1[x].size()) q2[fa[x]].push(q1[x].top());
if (q2[fa[x]].size() >= ) q3.push(q2[fa[x]].top()+q2[fa[x]].sec());
}
}
void turnoff(int o) {
if (q2[o].size() >= ) q3.erase(q2[o].top()+q2[o].sec());
q2[o].push();
if (q2[o].size() >= ) q3.push(q2[o].top()+q2[o].sec());
for (int x = o; fa[x]; x = fa[x]) {
if (q2[fa[x]].size() >= ) q3.erase(q2[fa[x]].top()+q2[fa[x]].sec());
if (q1[x].size()) q2[fa[x]].erase(q1[x].top());
q1[x].push(LCA::dist(fa[x], o));
q2[fa[x]].push(q1[x].top());
if (q2[fa[x]].size() >= ) q3.push(q2[fa[x]].top()+q2[fa[x]].sec());
}
}
void work() {
scanf("%d" ,&n);
for (int i = ; i < n; i++) {
scanf("%d%d", &u, &v);
add(u, v), add(v, u);
}
LCA::main(); Point_divide::main();
int cnt = n; scanf("%d", &m);
while (m--) {
scanf("%s", ch);
if (ch[] == 'G') {
if (cnt <= ) printf("%d\n", cnt-);
else printf("%d\n", q3.top());
}else {
scanf("%d", &u);
if (light[u]) turnoff(u), cnt++;
else turnon(u), cnt--;
light[u] ^= ;
}
}
}
int main() {
work();
return ;
}
[ZJOI 2007]Hide 捉迷藏的更多相关文章
- [BZOJ 1095] [ZJOI 2007]Hide 捉迷藏
在BZ上连续MLE n次后,终于A了. 自己YY的动态点分写法,思路还是很清楚的,但是比较卡内存. 用到了MAP导致复杂度比其他的代码多了一个log,看来需要去借鉴一下别人怎么写的. updata i ...
- 动态点分治:Bzoj1095: [ZJOI2007]Hide 捉迷藏
简介 这是我自己的一点理解,可能写的不好 点分治都学过吧.. 点分治每次找重心把树重新按重心的深度重建成了一棵新的树,称为分治树 这个树最多有log层... 动态点分治:记录下每个重心的上一层重心,这 ...
- 【BZOJ 1095】 1095: [ZJOI2007]Hide 捉迷藏 (括号序列+线段树)
1095: [ZJOI2007]Hide 捉迷藏 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏 ...
- 【BZOJ1095】[ZJOI2007]Hide 捉迷藏 动态树分治+堆
[BZOJ1095][ZJOI2007]Hide 捉迷藏 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉 ...
- [bzoj1095][ZJOI2007]Hide 捉迷藏 点分树,动态点分治
[bzoj1095][ZJOI2007]Hide 捉迷藏 2015年4月20日7,8876 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiaji ...
- BZOJ_1095_[ZJOI2007]Hide 捉迷藏_动态点分治+堆
BZOJ_1095_[ZJOI2007]Hide 捉迷藏_动态点分治+堆 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子 ...
- 数据结构(括号序列,线段树||点分治,堆):ZJOI 2007 捉迷藏
[题目描述] Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N- ...
- [BZOJ 1095] [ZJOI 2007] 捉迷藏
Description 传送门 Solution 先将原树转化成点分树: 然后维护三个堆: \(c[i]\) 保存点分树中子树 \(i\) 中的黑色节点到 \(fa[i]\) 的距离: \(b[i]\ ...
- [ZJOI 2007] 捉迷藏
[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1095 [算法] 首先建出点分树,然后每一个点开两个堆.“第一个堆记录子树中所有节点到 ...
随机推荐
- JavaWeb学习笔记九 过滤器、注解
过滤器Filter filter是对客户端访问资源的过滤,符合条件放行,不符合条件不放行,并且可以对目标资源访问前后进行逻辑处理. 步骤: 编写一个过滤器的类实现Filter接口 实现接口中尚未实现的 ...
- 《结对-HTML贪吃蛇游戏项目-测试过程》
项目托管平台地址:https://gitee.com/zhaojianhuiAA/TanChiShe/blob/master/snake.html 项目成员:赵建辉.马壮. 测试: 1.界面:用jav ...
- 201621123060《JAVA程序设计》第一周学习总结
1.本周学习总结 1.讲述了JAVA的发展史,关于JDK.JRE.JVM的联系和区别 2.JDK是用JAVA开发工具.做项目的关键.JRE是JAVA的运行环境(JAVA也是JAVA语言开发的).JVM ...
- 【iOS】Swift GCD-下
欢迎来到本GCD教程的第二同时也是最终部分! 在第一部分中,你学到了并发,线程以及GCD的工作原理.通过使用dispatch_barrrier和dispatch_sync,你做到了让PhotoMana ...
- "一不小心就火了"团队采访
团队采访 一. 采访团队 团队:一不小心就火了 采访形式:线上问答 二.采访内容 你们是怎么合理地具体分配组员里的工作的?有些团队会出现个别组员代码任务很重,个别组员无所事事的情况,你们有什么有效的方 ...
- java8-Stream之数值流
在Stream里元素都是对象,那么,当我们操作一个数字流的时候就不得不考虑一个问题,拆箱和装箱.虽然自动拆箱不需要我们处理,但依旧有隐含的成本在里面.Java8引入了3个原始类型特化流接口来解决这个问 ...
- tomcat下的web.xml和项目中的web.xml
Tomcat 服务器中存在一个web.xml文件 在项目文件夹中同样存在一个web.xml文件 那这两个文件有什么区别呢? tomcat中的web.xml是通用的,如果不设置,那么就会默认是同tomc ...
- TFTP通信原理
TFTP的通信流程 TFTP共定义了五种类型的包格式,格式的区分由包数据前两个字节的Opcode字段区分,分别是: · l 读文件请求包:Read request,简写为RRQ,对应Opcode字段值 ...
- Java排序算法之快速排序
Java排序算法之快速排序 快速排序(Quicksort)是对冒泡排序的一种改进. 快速排序由C. A. R. Hoare在1962年提出.它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分 ...
- python入门(4)第一个python程序
python入门(4)第一个python程序 在交互式环境的提示符>>>下,直接输入代码,按回车,就可以立刻得到代码执行结果.现在,试试输入100+200,看看计算结果是不是300: ...