题意简述

给定一棵 \(n\) 个点的树,起初每个点都为黑色。

2种操作,要么改变某个点的颜色(由黑至白或由白至黑),要么询问距离最远的两个黑点间的距离。

共 \(m\) 次操作。

\(n\leq 10^5,m\leq 2\times 10^5\)


想法

动态点分治模板题。

如果只有一次询问操作,那么显然可以用点分治来做。(树形 \(dp\) 也可以,但那样不容易拓展到动态的情况)

点分治时,以每个点为根时,统计过它的满足条件的路径即可。

我们需要知道的只是以该点 \(u\) 的每个子节点 \(v\) 为根的子树到该点的最长距离 \(mx[v]\)。

过该点的路径的最长距离为 所有 \(mx[v]\) 中的最大值+次大值。注意如果该点为黑点,最长距离则为 所有 \(mx[v]\) 和 \(0\) 中的最大值+次大值。

同时我们应统计出以 \(u\) 为根的子树中所有点到上一个分治中心的距离的最大值 \(mx[u]\)。

回到这道题,有修改和多次查询。

于是建立 “点分树”,即将当前分治中心与它的各子节点的分治中心连边形成的树。

这棵树有一些性质:

1.只有 \(logn\) 层

2.原树中以每个分治中心为根的子树里的所有点 就是 点分树中以它为根的子树中的所有点。

3.修改一个点 \(x\) ,影响到的是点分树上所有 \(x\) 的祖先为分治中心的情况。

设“点分树”中点 \(u\) 的父节点为 \(pa[u]\)

在此题中,对于每个点 \(u\),维护堆 \(c[u]\) 记录 \(u\) 为分治中心的子树中的所有黑点到 \(pa[u]\) 的距离,堆 \(b[u]\) 记录 \(u\) 为分治中心时各子节点到 \(u\) 的最大距离。即 \(b[u]\) 中的值,是所有 $c[v].top(),如果 \(u\) 为黑点则还需加上0。

对全局维护堆 \(a\) 记录过每个分治中心的最长距离。\(a\) 中的值,就是 \(b[u]\) 中的最大值+次大值。

由于有修改,所以堆需要满足可删除。

用两个优先队列维护一个堆即可。

还有一个问题,如何快速求原树上两点间距离?

我们知道倍增和树剖都是 \(O(logn)\) 的,但更快的方法是 \(st\) 表+ \(dfs\) 序。

这个 \(dfs\) 序很特殊,每次访问完 \(u\) 的子节点 \(v\) 后,要在序列中再加入 \(u\) 。记录进入每个点的时间 \(dfn[u]\)

在这个序列上用 \(O(nlogn)\) 预处理出 \(st\) 表,之后查询 \(x\) 与 \(y\) 的 \(lca\) 就是 \(dfn[x]\) 与 \(dfn[y]\) 着一段序列中深度最小的点,\(O(1)\) 可求。


总结

技巧

1.\(O(1)\) 求静态树上两点的 \(lca\):\(st\) 表+ \(dfs\) 序

int tot,dfn[N],num[N*2],dep[N];
void dfs(int u){
int v;
dfn[u]=++tot; num[tot]=u;
for(node *p=h1[u];p;p=p->nxt)
if(!dfn[v=p->v]) {
dep[v]=dep[u]+1;
dfs(v);
num[++tot]=u; //与普通dfs序不同的地方!
}
}
int st[N*2][18],lg[N*2];
void getst(){
dep[1]=1; dfs(1);
for(int i=1;i<=tot;i++) st[i][0]=dep[num[i]];
for(int j=1;j<18;j++){
int t=(1<<j);
for(int i=1;i+t-1<=tot;i++)
st[i][j]=min(st[i][j-1],st[i+t/2][j-1]);/**/
}
int t=0,cur=1;
for(int i=1;i<=tot;i++)
if(i<cur) lg[i]=t-1;
else {
lg[i]=t;
t++; cur*=2;
}
}
int lca(int x,int y){ //lca的深度
x=dfn[x]; y=dfn[y];
if(x>y) swap(x,y);
int t=lg[y-x+1];
return min(st[x][t],st[y+1-(1<<t)][t]);
}

2.可删堆

用两个优先队列维护,一个维护所有的,一个维护删除的。

struct heap{
priority_queue<int> q,d;
void ins(int x) { q.push(x); }
void del(int x) { d.push(x); }
void pop(){ //删除最大值
while(d.size() && d.top()==q.top()) q.pop(),d.pop();
q.pop();
}
int fr(){ //堆中最大值
while(d.size() && d.top()==q.top()) q.pop(),d.pop();
return q.top();
}
int size() { return q.size()-d.size(); }
};

手残

\(st\) 表中注意第二维不要开小了!

注意判断堆是否为空。


代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue> using namespace std; int read(){
int x=0;
char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x;
} const int N = 100005; int n;
struct node{
int v;
node *nxt;
}pool[N*4],*h1[N],*h[N];
int cnt;
void addedge1(int u,int v){
node *p=&pool[++cnt],*q=&pool[++cnt];
p->v=v;p->nxt=h1[u];h1[u]=p;
q->v=u;q->nxt=h1[v];h1[v]=q;
}
void addedge(int u,int v){
node *p=&pool[++cnt],*q=&pool[++cnt];
p->v=v;p->nxt=h[u];h[u]=p;
q->v=u;q->nxt=h[v];h[v]=q;
} //get lca
int tot,dfn[N],num[N*2],dep[N];
void dfs(int u){
int v;
dfn[u]=++tot; num[tot]=u;
for(node *p=h1[u];p;p=p->nxt)
if(!dfn[v=p->v]) {
dep[v]=dep[u]+1;
dfs(v);
num[++tot]=u; /**/
}
}
int st[N*2][18],lg[N*2];
void getst(){
dep[1]=1; dfs(1);
for(int i=1;i<=tot;i++) st[i][0]=dep[num[i]];
for(int j=1;j<18;j++){
int t=(1<<j);
for(int i=1;i+t-1<=tot;i++)
st[i][j]=min(st[i][j-1],st[i+t/2][j-1]);/**/
}
int t=0,cur=1;
for(int i=1;i<=tot;i++)
if(i<cur) lg[i]=t-1;
else {
lg[i]=t;
t++; cur*=2;
}
}
int lca(int x,int y){
x=dfn[x]; y=dfn[y];
if(x>y) swap(x,y);
int t=lg[y-x+1];
return min(st[x][t],st[y+1-(1<<t)][t]);
}
int dis(int x,int y) {
return x==0||y==0?dep[x+y]:dep[x]+dep[y]-2*lca(x,y);
} //heap
struct heap{
priority_queue<int> q,d;
void ins(int x) { q.push(x); }
void del(int x) { d.push(x); }
void pop(){
while(d.size() && d.top()==q.top()) q.pop(),d.pop();
q.pop();
}
int fr(){
while(d.size() && d.top()==q.top()) q.pop(),d.pop();
return q.top();
}
int se(){
int x=fr(); pop();
int y=fr(); q.push(x);
return x+y;
}
int size() { return q.size()-d.size(); }
}a,b[N],c[N]; //build dianfen tree
int rt,root,all,sz[N],mx[N],vis[N],pa[N];
void getrt(int u,int fa){
int v;
sz[u]=1; mx[u]=0;
for(node *p=h1[u];p;p=p->nxt)
if((v=p->v)!=fa && !vis[v]){
getrt(v,u);
sz[u]+=sz[v];
mx[u]=max(mx[u],sz[v]);
}
mx[u]=max(mx[u],all-sz[u]);
if(mx[u]<mx[rt]) rt=u;
}
void getsz(int u,int fa,int id){
int v;
sz[u]=1;
c[id].ins(dis(u,pa[id]));
for(node *p=h1[u];p;p=p->nxt)
if((v=p->v)!=fa && !vis[v]){
getsz(v,u,id);
sz[u]+=sz[v];
}
}
void work(int u){
int v;
vis[u]=1;
c[u].ins(dis(u,pa[u]));
b[u].ins(0);
for(node *p=h1[u];p;p=p->nxt)
if(!vis[v=p->v]){
getsz(v,u,u);
all=sz[v]; rt=0; getrt(v,u);
addedge(u,rt);
pa[rt]=u; v=rt;
work(rt);
b[u].ins(c[v].fr());
}
if(b[u].size()>1) a.ins(b[u].se());
} //modify
void turn_off(int u){//1->0
int x=u;
if(b[x].size()>1) a.del(b[x].se());
b[x].ins(0);
if(b[x].size()>1) a.ins(b[x].se());
while(x){
if(pa[x]) { //del fa
if(b[pa[x]].size()>1) a.del(b[pa[x]].se());
if(c[x].size()) b[pa[x]].del(c[x].fr()); /**/
}
c[x].ins(dis(u,pa[x]));
if(pa[x]) { //update fa
b[pa[x]].ins(c[x].fr());
if(b[pa[x]].size()>1) a.ins(b[pa[x]].se());
}
x=pa[x]; /**/
}
}
void turn_on(int u){//0->1
int x=u;
if(b[x].size()>1) a.del(b[x].se());
b[x].del(0);
if(b[x].size()>1) a.ins(b[x].se());
while(x){
if(pa[x]) { //del fa
if(b[pa[x]].size()>1) a.del(b[pa[x]].se());
if(c[x].size()) b[pa[x]].del(c[x].fr()); /**/
}
c[x].del(dis(u,pa[x]));
if(pa[x]) { //update fa
if(c[x].size()) b[pa[x]].ins(c[x].fr());/**/
if(b[pa[x]].size()>1) a.ins(b[pa[x]].se());
}
x=pa[x];/**/
}
} int lon,lit[N]; int main()
{
int Q,x;
char ch[2];
n=read();
for(int i=1;i<n;i++) addedge1(read(),read()); getst();
rt=0; mx[rt]=n+1; all=n; getrt(1,0);
root=rt;
work(root); lon=0;
Q=read();
while(Q--){
scanf("%s",ch);
if(ch[0]=='C'){
x=read();
if(lit[x]) lon--,turn_off(x),lit[x]=0; /*lit*/
else lon++,turn_on(x),lit[x]=1;
}
else{
if(lon==n) printf("-1\n");
else if(lon==n-1) printf("0\n");
else printf("%d\n",a.fr());
}
} return 0;
}

[bzoj1905] [ZJOI2007] Hide 捉迷藏的更多相关文章

  1. 动态点分治:Bzoj1095: [ZJOI2007]Hide 捉迷藏

    简介 这是我自己的一点理解,可能写的不好 点分治都学过吧.. 点分治每次找重心把树重新按重心的深度重建成了一棵新的树,称为分治树 这个树最多有log层... 动态点分治:记录下每个重心的上一层重心,这 ...

  2. 【BZOJ 1095】 1095: [ZJOI2007]Hide 捉迷藏 (括号序列+线段树)

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

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

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

  4. [bzoj1095][ZJOI2007]Hide 捉迷藏 点分树,动态点分治

    [bzoj1095][ZJOI2007]Hide 捉迷藏 2015年4月20日7,8876 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiaji ...

  5. BZOJ_1095_[ZJOI2007]Hide 捉迷藏_动态点分治+堆

    BZOJ_1095_[ZJOI2007]Hide 捉迷藏_动态点分治+堆 Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子 ...

  6. [ZJOI2007]Hide 捉迷藏

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

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

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

  8. BZOJ1095: [ZJOI2007]Hide 捉迷藏【线段树维护括号序列】【思维好题】

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

  9. 「BZOJ1095」[ZJOI2007] Hide 捉迷藏

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

随机推荐

  1. [Python]爬取 游民星空网站 每周精选壁纸(1080高清壁纸) 网络爬虫

    一.检查 首先进入该网站的https://www.gamersky.com/robots.txt页面 给出提示: 弹出错误页面 注: 网络爬虫:自动或人工识别robots.txt,再进行内容爬取 约束 ...

  2. LeetCode 56. Merge Intervals 合并区间 (C++/Java)

    题目: Given a collection of intervals, merge all overlapping intervals. Example 1: Input: [[1,3],[2,6] ...

  3. 牛客网剑指offer第13题——调整数组顺序使得奇数位于偶数前面

    题目来源:剑指offer 题目: 输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变 ...

  4. FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecate;的解决办法

    踩坑场景 报错FutureWarning: Passing (type, 1) or '1type' as a synonym of type is deprecate; 解决办法 1.升级numpy ...

  5. 【pycharm基本操作】项目创建、切换、运行、字体颜色设置,常见包的安装步骤

    创建新项目 退出项目 怎样区别虚拟环境和系统环境? 虚拟环境和系统环境切换:进入项目切换解释器 切换项目 创建python目录和文件 代码运行方式一: 还可以这样执行代码方式二: 文件的剪切.复制.删 ...

  6. Android开发第一天---AndroidStudio的安装和第一个安卓开发

    今天已经是开始学习Android的第二天,我居然才把AndroidStudio开发环境安装并配置好,我只能说“我太难了”,下了好几个版本,终于找到了一个合适的,得出一个结论外国的东西是真的不太好用啊, ...

  7. 在写论文的参考文献时,有的段落空格很大,有的段落则正常,原因及解决方法(wps)

    下图是一段原始的参考文献,可以看出第一行的空格很大: 原因: 当一个词占不下时,自动将单词移动到下一行,但是这一行又有很多字符,因此这时,软件会将空闲的位置用空白字符填满.第一行有两个空白字符,因此将 ...

  8. 对Linux内核tty设备的一点理解(转)

    虽然一直做嵌入式Linux,宿主机和开发板通信天天都在用tty设备通信,但是其实自己对TTY设备及终端的概念认识几乎是0.对于Linux内核的终端.tty.控制台等概念的认识很模糊.由于在学习的时候碰 ...

  9. PHP Magic Method Setter and Getter

    <?php /* Magic method __set() and __get() 1.The definition of a magic function is provided by the ...

  10. VMware桥接模式下虚拟机ping主机不通

    现象: VMware设置为桥接模式,虚拟机ping主机不通,主机ping虚拟机通. 解决: 尝试以下几种方法 关闭主机(专用网络.来宾或公用网络)和虚拟机的防火墙. 更改桥接的物理网卡,确保是主机正在 ...