动态点分治入门 ZJOI2007 捉迷藏
这道题好神奇啊……如果要是不带修改的话那就是普通的点分治了,每次维护子树中距离次大值和最大值去更新。
不过这题要修改,而且还改500000次,总不能每改一次都点分治一次吧。
所以我们来认识一个新东西:带修改的点分治,动态点分治!
它可以强势解决带修改点分治问题(但是这玩意真的太难了我这个菜鸡只能学到入门)
首先我们要建立一棵树(点分树),这棵树是由点分治每次所分治的所有子树的重心串起来的。为什么要这么做呢?因为对于每次的修改,其实并没有影响到特别多的结果,它只会影响它自己所在的子树的重心,以及这个所对应重心在点分树上的祖先。
为什么可以这样做?因为我们考虑到,点分治中,每一个重心其实只会维护自己的子树中的情况,其他的情况于这个重心是不相干的。所以其实对于一次修改,它能影响的就是它所在子树重心的答案(这个是显然的),然后对于这个重心,它所在的树必然是它自己在点分树上的父亲(也就是一棵更大的树的重心,当前树是其子集)的一棵小子树,所以也会对之产生影响,然后再往上递归也是同理,这样的话,我们使用点分树维护,每次就只需要更改log个节点。
建立点分树怎么建?听起来或许很难但实际上特别简单,因为我们本身就是递归访问的,每次在递归求完子树重心的时候只要记录一下它当前的父亲是谁(就是当前的重心)就可以了。
先看一下代码。
void solve(int x)
{
vis[x] = ;
for(int i = head[x];i;i = e[i].next)
{
int t = e[i].to;
if(vis[t]) continue;
sum = sz[t],G = ;
getG(t,x),fq[G] = x;solve(G);
}
}
其中建树的就是fq[G] = x那一行……是不是非常好做?
然后假设我们现在建完了这棵点分树(也就是我们遍历了整棵树),之后对于无穷无尽的修改操作我们该咋办呢……
我们还是想刚才那个事,如果要是不修改的话,我们只要求出来当前重心所有子树中的最长和次长距离来更新答案就可以,虽然现在带上修改了,但是我们计算答案的方法是并不会变的!
所以我们发现了这几件事:
1.我们可以对于每一个重心(点分树上的每一个节点)都开一个堆来维护当前子树中的最大值(C堆)
2.对于每一个重心,再开一个堆,记录它所有子树中的距离最大值和次大值(也就是上面C堆中两个最大的堆顶元素)(B堆)
3.开一个全局的堆,记录所有重心的距离最大值和次大值(也就是B堆中两个最大的堆顶元素)(A堆)
这样我们就可以进行维护了!每次修改是log的,用堆维护也是log的,总复杂度是log^2的。
说的如此轻描淡写该咋做呀……(这玩意简直不是一般难写,而且还贼难理解)
我们不选择使用set而是开一个结构体,里面有两个堆,其中一个用来存有用的状态一个存无用状态。(啥叫有用无用状态?)我们直接用set查找元素进行删除其实比较慢,我们可以这样做,每次把要删除的状态存到另一个堆里面,等真正要进行删除操作的时候,我们再将其删除。
是不是感觉没听懂?对其实我也不大懂。
大致意思就是,因为一些修改操作使得一些状态变得不合法,我们不用什么find函数之类的直接给他删了,而是加到无用状态里,只有在堆顶是无用状态的时候我们把其删除即可。
好。之后我们先考虑把白点变黑点的操作(我们姑且称之为“关灯操作”)
我们每次求的时候,首先更新一下当前节点的B堆,把一个0的情况加进去(因为你相当于没走嘛),之后如果当前的B的大小是2,也就说明有了最大和次大值,我们就向A堆里面添加一下这个值。
之后我们先计算一下当前重心的在点分树上的父亲和这点的距离。因为你是把当前点变成了黑点,所以这个点的答案必然是合法的,我们把其压入当前的C堆中。之后,如果这个值大于当前C堆中的最大值,那说明我们这次修改对这个范围的答案是有影响的,肯定是要进行一次修改了。那我们先统计这个重心的父亲的B堆中的最大和次大值,之后把堆顶元素弹出(因为当前这个值已经更大了说明它没用了),把这个更大的答案加进去。之后再次计算当前重心的父亲的B堆中的最大和次大值,如果要是比原来大,并且B中有两个以上的元素(说明有最大值和次大值,只有一个是不能删除的,因为旧的答案可以成为次大值),那么我们在A堆中把这个答案删除,再把新答案添加进去即可。注意添加答案的前提是,B堆中至少也有两个元素,这样才保证有了最大值和次大值。才可以更新。
之后我们重复上述操作,向上递归更新。这样关灯操作就完成了。
与之对应的是开灯操作。
开灯操作大部分都是相对应的,因为开灯之后,我们的答案将变得不合法,所以我们需要删去这些答案。
这里的操作只有在你当前的答案就是堆顶元素的时候才会去更新。更新的方法和上面都是对应且相反的,直接看代码即可。
然后最后一个问题是,如何O(1)求出树上两点之间的距离。这个可以使用dfs序,st表转化为RMQ问题解决(这个要好好复习了)
所以我们总结一下做这题的步骤。
1.先手点分治,把点分树建出来同时遍历每棵树,确定初始的最长距离。
2.初始化关于RMQ的一些数组和函数
3.建立三个堆,每个堆里面再用两个堆去模拟set进行维护
4.把所有的点全部关一次灯。
5.开始修改,每次修改对应上面的开,关灯操作,每次输出结果即可。
最后还有啥不懂的看一下代码,要是还不懂我们慢慢来(其实我也没完全理解,还是慢慢来)
// luogu-judger-enable-o2//这题不开O2的话会T……
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
#include<queue>
#include<set>
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
#define enter putchar('\n') using namespace std;
typedef long long ll;
const int M = ;
const int INF = 1e9+; int read()
{
int ans = ,op = ;
char ch = getchar();
while(ch < '' || ch > '')
{
if(ch == '-') op = -;
ch = getchar();
}
while(ch >= '' && ch <= '')
{
ans *= ;
ans += ch - '';
ch = getchar();
}
return ans * op;
} int n,m,G,ecnt,sum,fq[M],dep[M],maxs[M],sz[M],head[M],x,y;
int anc[][M<<],tot1,bin[],Log[M],dfn,num[M];
bool col[M],vis[M]; struct edge
{
int next,to;
}e[M<<]; void add(int x,int y)
{
e[++ecnt].to = y;
e[ecnt].next = head[x];
head[x] = ecnt;
}
struct heap
{
priority_queue<int> A,B;//优先队列模拟堆
void push(int x)//添加一个状态
{
A.push(x);
}
void erase(int x) //删除状态
{
B.push(x);
}
void pop()//把A堆的堆顶元素弹出
{
while(B.size() && (A.top() == B.top())) A.pop(),B.pop();
A.pop();
}
int top()//求A堆堆顶元素
{
while(B.size() && (A.top() == B.top())) A.pop(),B.pop();
if(!A.size()) return ;
return A.top();
}
int size()//返回当前状态数 = 总状态-无用状态
{
return A.size() - B.size();
}
int stop()//求A堆次大元素
{
if(size() < ) return ;
int x = top();pop();
int y = top();push(x);
return y;
}
}A,B[],C[]; void init()//这个是处理2的幂和每个数对应的log值,RMQ初始化
{
bin[] = ;rep(i,,) bin[i] = bin[i-] << ;
Log[] = -;rep(i,,) Log[i] = Log[i>>] + ;
} void dfs(int x,int fa)//同样是RMQ初始化,记录dfs序
{
anc[][++dfn] = dep[x],num[x] = dfn;
for(int i = head[x];i;i = e[i].next)
{
int t = e[i].to;
if(t == fa) continue;
dep[t] = dep[x] + ;
dfs(t,x);
anc[][++dfn] = dep[x];
}
} void ST()//ST表操作
{
rep(i,,Log[dfn])
rep(j,,dfn)
if(j + bin[i] - <= dfn)
anc[i][j] = min(anc[i-][j],anc[i-][j + bin[i-]]);
} int RMQ(int x,int y)//真实RMQ
{
x = num[x],y = num[y];
if(x > y) swap(x,y);
int t = Log[y-x+];
return min(anc[t][x],anc[t][y-bin[t]+]);
} int dis(int x,int y)//计算两点之间距离!
{
return dep[x] + dep[y] - * RMQ(x,y);
} void getG(int x,int fa)//找重心
{
sz[x] = ,maxs[x] = ;
for(int i = head[x];i;i = e[i].next)
{
int t = e[i].to;
if(t == fa || vis[t]) continue;
getG(t,x);
sz[x] += sz[t];
maxs[x] = max(maxs[x],sz[t]);
}
maxs[x] = max(maxs[x],sum - sz[x]);
if(maxs[x] < maxs[G]) G = x;
} void solve(int x)//点分治+建立点分树
{
vis[x] = ;
for(int i = head[x];i;i = e[i].next)
{
int t = e[i].to;
if(vis[t]) continue;
sum = sz[t],G = ;
getG(t,x),fq[G] = x;solve(G);//在这里建立点分树
}
} void turnoff(int x,int v)//关灯
{
if(x == v)//第一个节点
{
B[x].push();
if(B[x].size() == ) A.push(B[x].top());//有最大和次大即更新
}
if(!fq[x]) return;
int f = fq[x],D = dis(f,v),tmp = C[x].top();//计算当前两点间距离和这个点C堆的最大值
C[x].push(D);//把合法答案压入
if(D > tmp)//如果这个值更优,说明修改产生了影响,要更新
{
int maxn = B[f].top() + B[f].stop(),size = B[f].size();//计算当前最大值(最大和次大更新)和B的大小
if(tmp) B[f].erase(tmp);//把这个无用的删了
B[f].push(D);//把有用的加进来
int cur = B[f].top() + B[f].stop();//重计算一下答案
if(cur > maxn)//如果新答案更优
{
if(size >= ) A.erase(maxn);//把这个无用的删了
if(B[f].size() >= ) A.push(cur);//把这个新的加进来
}
}
turnoff(f,v);//继续向上递归关灯
} void turnon(int x,int v)//开灯
{
if(x == v)
{
if(B[x].size() == ) A.erase(B[x].top());
B[x].erase();//和上面是正好相反的
}
if(!fq[x]) return;
int f = fq[x],D = dis(f,v),tmp = C[x].top();
C[x].erase(D);//把这个答案给删了(不合法)
if(D == tmp)//如果这个答案=堆顶元素,说明这次修改产生了影响
{
int maxn = B[f].top() + B[f].stop(),size = B[f].size();
B[f].erase(D);
if(C[x].top()) B[x].push(C[x].top());
int cur = B[f].top() + B[f].stop();
if(cur < maxn)//这些和上面都是相同的操作了,注意这次变成了小于
{
if(size >= ) A.erase(maxn);
if(B[f].size() >= ) A.push(cur);
}
}
turnon(f,v);//递归向上开灯
} int main()
{
init();n = read();
rep(i,,n-) x = read(),y = read(),add(x,y),add(y,x);
dfs(,),ST();//前面都预处理出来
sum = n;maxs[G] = INF;
getG(,);
fq[G] = ,solve(G);//找到重心开始建立点分树
rep(i,,n) col[i] = ,turnoff(i,i),tot1++;//把每个点都关灯
m = read();
while(m--)//开始修改
{
char c = getchar();
if(c == 'G')
{
if(tot1 <= ) printf("%d\n",tot1-);//要是只有一个点那就是0,要是没有直接输出-1,tot1记录当前黑点数
else printf("%d\n",A.top());//否则输出最大值
}
else
{
x = read();
if(col[x]) turnon(x,x),tot1--;//开灯
else turnoff(x,x),tot1++;//关灯
col[x] ^= ;//转变开关灯情况
}
}
return ;
}
动态点分治入门 ZJOI2007 捉迷藏的更多相关文章
- 动态点分治:Bzoj1095: [ZJOI2007]Hide 捉迷藏
简介 这是我自己的一点理解,可能写的不好 点分治都学过吧.. 点分治每次找重心把树重新按重心的深度重建成了一棵新的树,称为分治树 这个树最多有log层... 动态点分治:记录下每个重心的上一层重心,这 ...
- 【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和孩子 ...
- BZOJ1095: [ZJOI2007]Hide 捉迷藏【动态点分治】
Description 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩 捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条 ...
- 【bzoj1095】[ZJOI2007]Hide 捉迷藏 动态点分治+堆
题目描述 捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这 ...
- [ZJOI2007]捉迷藏(动态点分治/(括号序列)(线段树))
题目描述 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子.某天,Jiajia.Wind和孩子们决定在家里玩捉迷藏游戏.他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条 ...
- BZOJ1095 [ZJOI2007]Hide 捉迷藏 动态点分治 堆
原文链接https://www.cnblogs.com/zhouzhendong/p/BZOJ1095.html 题目传送门 - BZOJ1095 题意 有 N 个点,每一个点是黑色或者白色,一开始所 ...
- 2019.01.10 bzoj1095: [ZJOI2007]Hide 捉迷藏(动态点分治)
传送门 蒟蒻真正意义上做的第一道动态点分治! 题意:给一棵最开始所有点都是黑点的树,支持把点的颜色变成从黑/白色变成白/黑色,问当前状态树上两个最远黑点的距离. 思路: 首先考虑不带修改一次点分治怎么 ...
随机推荐
- 最大数(cogs 1844)
[题目描述] 现在请求你维护一个数列,要求提供以下两种操作: 1. 查询操作.语法:Q L 功能:查询当前数列中末尾L个数中的最大的数,并输出这个数的值.限制:L不超过当前数列的长度. 2. 插入操作 ...
- Java设计模式之(工厂模式)
工厂模式: 工厂模式可以分为三类: 1)简单工厂模式(Simple Factory) 2)工厂方法模式(Factory Method) 3)抽象工厂模式(Abstract Factory) 简单工厂模 ...
- 【HDOJ6312】Game(博弈)
题意: 有一个1到n的序列,两个人轮流取数,取走一个数同时会取走它所有的因子,不能取者为输,两个人都按最优策略取数,问先手是否必胜 思路: #include<cstdio> #includ ...
- 内存管理(——高质量程序设计语言C/C++第16章)
内存的分配方式: 1.静态存储区分配:全局变量,static变量等,在程序编译时已经分配了存储内存,在程序运行的整个期间一直存在 2.程序的堆栈上:程序的局部变量,包括程序的形参等,只存在于程序的运行 ...
- windows7 下安装使用memcached(二)
Memcached 安装使用 本地环境:Windows7 64位web环境:wamp集成环境,php版本:PHP Version 7.1.17 学习参考网站: RUNOOB.COM官网 http:/ ...
- Visual Studio 2017 RC的坑
ASP.NET Core Project add Docker Project Support的问题 执行上面操作以后,如果本机没有装好docker,就会一直报错,无法build通过,无论你在Proj ...
- Space Ant--poj1696(极角排序)
http://poj.org/problem?id=1696 极角排序是就是字面上的意思 按照极角排序 题目大意:平面上有n个点然后有一只蚂蚁他只能沿着点向左走 求最多能做多少点 分析: 其实 ...
- 洛谷——P1596 [USACO10OCT]湖计数Lake Counting
P1596 [USACO10OCT]湖计数Lake Counting 题目描述 Due to recent rains, water has pooled in various places in F ...
- CodeForces 582A【multiset使用样例】
题意: 给一些无序的数字,求解一个矩阵,使得矩阵的每一个元素都是行和列标志数的gcd,输出行标志数. 首先对数字进行排序.复杂度n*log(n^2). 这题的证明有官方的英文题解==在这直接贴英文题解 ...
- chapter1:using neural nets to recognize handwritten digits
two important types of artificial neuron :the perceptron and the sigmoid neuron Perceptrons 感知机的输入个数 ...