题解 P2056 【[ZJOI2007]捉迷藏 】
ywy_c_asm
2018-11-27 16:56:56
作为一道动态点分治板子题,这题题解里没动态点分治解法真是不科学。~~虽然这东西多带一个log……~~
大致的思路就是我们在静态的点分治的基础上维护一个高度为$O(logn)$的树形结构,这棵树上的点为当前做点分治时这个连通块的重心,应该是很好想的毕竟分治本身就是个树形结构,我们把这个树形结构具体实现出来了而已。我们把这个树形结构称作点分树。
然后我们在点分树上维护一些奇奇怪怪的信息,就能够支持带动态修改的和普通点分治一样的路径信息统计了。因为修改的时候只需要考虑这个点在点分树上到根的路径上的点,而树高是$O(logn)$的就暴力跳就行了。
关于如何在点分树上维护这里写两种方法:
**方法1.**(~~这其实是我自己YY出来的……~~)普通点分治是以当前连通块的重心为根,然后逐个扫一遍他的儿子做类似于dp一样的东西,这其中的关键就是 **以重心为根合并他的两个儿子的子树** ,显然,我们需要在当前连通块的重心处用个支持删除的logn数据结构维护他所有儿子(这里说的都是原来树上的儿子,别和点分树上的儿子搞混了)子树内的关灯点到重心的距离的最大值和次大值,显然这两个加起来就是过这个点所有合法路径的最大值。而他的儿子的子树如何分开维护呢?我们可以用$dfs$序把儿子的子树当成一个区间,然后在这个重心上用动态开点线段树维护(显然这样做空间复杂度是$O(nlogn)$的)$dfs$序区间内的最大值,然后再在重心上开一棵平衡树维护这些区间的最大值。那么他点分树上的子树的答案呢?我们再开一棵平衡树维护点分树上子树的答案最大值,再用这棵平衡树的最大值更新点分树上父亲的答案。然后就可以在修改的时候维护这3棵树了,时间复杂度$O(nlog^2n)$,空间复杂度$O(nlogn)$。
然后这个方法代码极长(我目前写过的最长代码……),过于毒瘤,这还不算——它常数太大了,毕竟我们要同时在一条到根的链上维护3个数据结构,这其中还有个常数大的吓人的动态开点线段树……实测最后一个点过不去……
~~代码过于毒瘤就不放了……~~
**方法2.** 我们其实可以换一种思路统计答案,方法1说了我们得在重心处合并两个子树的答案,而子树我们还得在这一层想办法维护。但是我们发现**每个子树可是要在分治的下一层作为一个整体的连通块的**,那么我们可以在点分治的时候考虑他在点分树上的父亲$fa$,我们维护当前连通块内所有关灯点到$fa$距离的最大值,记为$mxdis_i$,然后我们在$fa$处维护他点分树的儿子的$mxdis_i$的最大值和次大值,就是这个点的答案了。至于维护总体的答案其实也不用在树上每个点都维护一遍,在全局用个数据结构维护就行了。注意维护点分树儿子最大的$mxdis_i$的时候,如果自己是关着灯的就得再额外插入一个0,因为自己也可以是端点。
另外这个数据结构需要支持查最大值、次大值和删除,其实可以不用平衡树,我们用一个两个大根堆$A$和$B$构成的玩意就可以维护。删除的时候并不考虑$A$的删除,而是把这个数插到$B$内,查最大值的时候如果发现$A$的堆顶和$B$的堆顶一样就说明这个数被删除过了,就同时弹掉。(感谢$PoPoQQQ$大神博客里写的这个技巧)
于是我们就用常数更小的堆代替了线段树和平衡树,而且维护起来也更方便了,代码瞬间短了许多。
上代码~
```cpp
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define inf 123456789
using namespace std;
namespace ywy{
inline int get(){//我的垃圾快读
int n=0;char c;while((c=getchar())||23333){
if(c>='0'&&c<='9')break;if(c=='-')goto s;
}n=c-'0';while((c=getchar())||23333){
if(c>='0'&&c<='9')n=n*10+c-'0';else return(n);
}s:while((c=getchar())||23333){
if(c>='0'&&c<='9')n=n*10-c+'0';else return(n);
}
}
void print(int num){
if(num>=10)print(num/10);putchar(num%10+'0');
}
inline char cget(){
char c;while((c=getchar())||23333)if(c>='A'&&c<='Z')return(c);
}
int heads[100001];
int fa[100001];//点分树上的父亲
int size[100001];//当前的子树大小,找重心用的
int que[100001];
unsigned char bv[100001];//点分治的时候这个点是否被solve了
typedef struct _dui{//把这两个大根堆构成的玩意封装起来
priority_queue<int> me,del;//del存删除的信息
inline int getmax(){
while(!me.empty()&&(!del.empty()&&me.top()==del.top()))me.pop(),del.pop();//弹出已经删除的堆顶
if(me.empty())return(-inf);return(me.top());
}
inline void remove(int num){
if(me.top()==num)me.pop();else del.push(num);
}
inline void insert(int num){
me.push(num);
}
inline int getsecond(){
int cjr=getmax();if(cjr==-inf)return(-inf);me.pop();int ywy=getmax();me.push(cjr);return(ywy);
}
}dui;
dui globe;//维护全局的答案
dui fuqin[100001];//维护当前连通块关灯的点到点分树父亲的最大距离
dui chs[100001];//维护儿子(当然也包括自己)最大的maxdis
typedef struct _b{
int dest;int nxt;
}bian;
bian memchi[200001];
int gn=1;
inline void add(int s,int t){
memchi[gn].dest=t;memchi[gn].nxt=heads[s];heads[s]=gn;gn++;
}
int dis[100001][18];//当前分治到这个深度时,到点分树父亲的距离
int rdeep[100001];//作为重心时的分治深度
unsigned char zt[100001];//是否关灯
int gdeep,tot,zx;
inline void bfs(int pt){//bfs求出距离
tot=0;
register int head=0,tail=1;
que[0]=pt;
dis[pt][gdeep]=1;
do{
int me=que[head];head++;tot++;
for(register int i=heads[me];i;i=memchi[i].nxt){
if(bv[memchi[i].dest])continue;
if(dis[me][gdeep]+1<dis[memchi[i].dest][gdeep]){
dis[memchi[i].dest][gdeep]=dis[me][gdeep]+1;
que[tail]=memchi[i].dest;tail++;
}
}
}while(head<tail);
}
void afs(int pt,int baba){//找重心
size[pt]=1;
int mx=0;
for(register int i=heads[pt];i;i=memchi[i].nxt){
if(bv[memchi[i].dest]||memchi[i].dest==baba)continue;
afs(memchi[i].dest,pt);
size[pt]+=size[memchi[i].dest];
mx=max(mx,size[memchi[i].dest]);
}
if(max(mx,tot-size[pt])<=tot/2)zx=pt;
}
int lstans[100001];//存放这个点的答案,方便维护全局的堆
int hexin;
void digui(int pt,int baba,int dp){//点分治
gdeep=dp;
bfs(pt);
afs(pt,0);
if(!baba)hexin=zx;
rdeep[zx]=dp;
if(baba){
for(register int i=0;i<tot;i++)fuqin[zx].insert(dis[que[i]][dp]);//维护maxdis
}
fa[zx]=baba;
int me=zx;bv[zx]=1;
for(register int i=heads[zx];i;i=memchi[i].nxt){
if(bv[memchi[i].dest])continue;
digui(memchi[i].dest,me,dp+1);
}
if(baba)chs[baba].insert(fuqin[me].getmax());//把maxdis插入父亲
chs[me].insert(0);//别忘了维护自己
globe.insert(lstans[me]=chs[me].getmax()+chs[me].getsecond());//当前点的答案
}
void ywymain(){
memset(dis,0x7f,sizeof(dis));
int n=get();
for(register int i=1;i<n;i++){
int s=get(),t=get();add(s,t);add(t,s);
}
digui(1,0,0);
int q=get();
int guan=n;//有多少灯关着
while(q){
q--;
char cmd=cget();
if(cmd=='G'){
if(guan==1)printf("0\n");else{
if(guan==0)printf("-1\n");
else print(globe.getmax()),putchar('\n');
}
}else{
int x=get();
if(zt[x])chs[x].insert(0),guan++;//这个灯关了就要额外插入0
else chs[x].remove(0),guan--;
int cur=x;
while(cur){
int cjr=chs[cur].getmax()+chs[cur].getsecond();
if(cjr!=lstans[cur]){
globe.remove(lstans[cur]);
globe.insert(lstans[cur]=cjr);//更新全局答案
}if(!fa[cur])break;
int dp=rdeep[cur];
cjr=fuqin[cur].getmax();
if(zt[x])fuqin[cur].insert(dis[x][dp]);
else fuqin[cur].remove(dis[x][dp]);//维护到点分树父亲的距离
int ywy=fuqin[cur].getmax();
if(ywy!=cjr){
chs[fa[cur]].remove(cjr);
chs[fa[cur]].insert(ywy);
}
cur=fa[cur];
}
zt[x]^=1;
}
}
}
}
int main(){
ywy::ywymain();return(0);
}
```