显然只有一次询问的话,可以用点分治来实现。
但是现在我们有多组询问,还带有修改,我们只能通过动态点分治来做了。
动态点分治的主要思想:省去每次点分治求重心的过程,直接预处理出来(因为树的形态不会改变),建立点分树。那么我们每次分治时只需按照点分树上的路径走就是了。
例如,对于这么一颗树:(样例,1为根)(感谢绘图网站https://csacademy.com/app/graph_editor/)
建出来的点分树是这样的:(3为根)
注意:点分树只是把在原树中点分治遍历的顺序建了出来,如果求 \(dis\) 或 \(lca\) 还是要在原树中求,不能在点分树上求,因为点分树改变了原树形态。所以如果题目的询问不是针对全局的,而是带有父子关系的(比如多次询问以 \(u\) 为根的子树中路径长度为 \(k\) 的路径个数)就不能建点分树了。
应该不能吧,不知道有没有一种玄学的方法
然后我们对于每个点,维护四个大根堆:
- \(dis1\),维护:在点分树中以 \(u\) 为根的子树中,所有灭灯的节点到 \(u\) 的 \(fa\) 的距离。
- \(erase1\),因为我们有时要从 \(dis1\) 中删去一些值,所以 \(erase1\) 维护在 \(dis1\) 中要删去的值。
- \(dis2\),维护:在点分树中 \(u\) 的所有儿子的 \(dis1\) 的堆顶。那么将 \(dis2\) 的 \(top1\) 和 \(top2\) 取出来,再相加,就是合法的经过 \(u\) 的最长路径的长度。
- \(erase2\),和 \(erase1\) 差不多,用来维护在 \(dis2\) 中要删去的值。
由于对于 \(dis1\) 和 \(dis2\) 都有一个删除堆,所以我把 \(dis1\)、\(erase1\) 封装在一起,称为 \(heap1\);\(dis2\)、\(erase2\)封装在一起,称为 \(heap2\)。那么这两个 \(heap\) 都可以实现 \(top1()\)、\(top2()\)、\(pop()\)、\(erase()\) 和 \(size()\) 操作。
然后我们维护一个全局 \(heap\):\(Ans\) 堆,同样有一个 \(erase\) 堆。\(Ans\)用来维护全局 \(dis2\) 堆的 \(top1\) 和 \(top2\) 之和。
那么如果询问,答案就是 \(Ans\) 堆堆顶。
考虑如果修改一个点,那么只会对它的所有祖先的 \(dis1\)、\(dis2\) 有影响。
那么我们记录下询问点 \(Qpoint=u\),然后让 \(u\) 往上跳,更新 \(dis1\) 和 \(dis2\)。
代码和注释如下:
#include<bits/stdc++.h>
#define N 100010
#define INF 0x7fffffff
using namespace std;
struct heap
{
priority_queue<int>q1,q2;
int size()
{
return q1.size()-q2.size();
}
void push(int x)
{
q1.push(x);
}
void erase(int x)
{
q2.push(x);
}
void pop()
{
while(!q2.empty()&&q1.top()==q2.top())
q1.pop(),q2.pop();
q1.pop();
}
int top()
{
while(!q2.empty()&&q1.top()==q2.top())
q1.pop(),q2.pop();
return q1.empty()?0:q1.top();
}
int top2()
{
if(size()<2)return 0;
int x=top();
pop();
int y=top();
push(x);
return y;
}
}q,q1[N],q2[N];
//q全局路径最大
//q1距离自己父亲距离最大
//q2距离自己距离最大(每个儿子仅有一条路径)
int n,Q,nn,root,sum;
int cnt,head[N],nxt[N<<1],to[N<<1];
int size[N],maxsize[N];
int d[N],f[N][17];
int fa[N],ans[N];
bool vis[N],open[N];
void adde(int u,int v)
{
to[++cnt]=v;
nxt[cnt]=head[u];
head[u]=cnt;
}
//------------------------------------------------------------倍增求点之间的距离
void dfs(int u)
{
for(int i=1;i<=16;i++)
f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==f[u][0])
continue;
f[v][0]=u;
d[v]=d[u]+1;
dfs(v);
}
}
int lca(int a,int b)
{
if(d[a]<d[b])
swap(a,b);
for(int i=16;i>=0;i--)
if(d[f[a][i]]>=d[b])
a=f[a][i];
if(a==b)
return a;
for(int i=16;i>=0;i--)
if(f[a][i]!=f[b][i])
a=f[a][i],b=f[b][i];
return f[a][0];
}
int getdis(int a,int b)
{
return d[a]+d[b]-2*d[lca(a,b)];
}
//------------------------------------------------------------建点分树
void getroot(int u,int fa)
{
size[u]=1,maxsize[u]=0;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(v==fa||vis[v])
continue;
getroot(v,u);
size[u]+=size[v];
maxsize[u]=max(maxsize[u],size[v]);
}
maxsize[u]=max(maxsize[u],nn-size[u]);
if(maxsize[u]<maxsize[root])
root=u;
}
void maketree(int u)
{
vis[u]=true;
for(int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(vis[v])
continue;
nn=size[v],root=0;
getroot(v,u);
fa[root]=u;//记录点分树中的fa
maketree(root);
}
}
//------------------------------------------------------------修改
void update(int u)
{
if(!open[u])
{
sum++;
q2[u].push(0);//push(0)是为了保证当灯是关着时,q2[u]的size至少为1
if(q2[u].size()==2)
q.push(q2[u].top());
}
else
{
sum--;
if(q2[u].size()==2)
q.erase(q2[u].top());
q2[u].erase(0);
}
int now=u;
while(1)
{
int dis=getdis(fa[now],u),t1;
if(!fa[now])
return;
if(!open[u])t1=q1[now].top(),q1[now].push(dis);//更新q1
else q1[now].erase(dis),t1=q1[now].top();
if(dis>t1)
{
int s1=q2[fa[now]].top()+q2[fa[now]].top2();
int siz=q2[fa[now]].size();
if(!open[u])//更新q2
{
if(t1)
q2[fa[now]].erase(t1);
q2[fa[now]].push(dis);
}
else
{
q2[fa[now]].erase(dis);
if(t1)
q2[fa[now]].push(t1);
}
int s2=q2[fa[now]].top()+q2[fa[now]].top2();
if(s2!=s1)//更新Ans堆
{
if(siz>=2)
q.erase(s1);
if(q2[fa[now]].size()>=2)
q.push(s2);
}
}
now=fa[now];
}
}
//------------------------------------------------------------主程序
int main()
{
scanf("%d",&n);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
adde(u,v),adde(v,u);
}
d[1]=1;
dfs(1);
nn=n,maxsize[0]=INF;
getroot(1,0);
maketree(root);
for(int i=1;i<=n;i++)
open[i]=true;
for(int i=1;i<=n;i++)
update(i),open[i]=false;
scanf("%d",&Q);
while(Q--)
{
char ch=getchar();
while(ch!='C'&&ch!='G')
ch=getchar();
if(ch=='G')
{
if(sum>=2) printf("%d\n",q.top());
else if(sum==1) puts("0");
else puts("-1");
}
if(ch=='C')
{
int u;
scanf("%d",&u);
open[u]^=1;
update(u);
}
}
return 0;
}
标签:q1,q2,fa,捉迷藏,分治,int,ZJOI2007,now,size
From: https://www.cnblogs.com/ez-lcw/p/16842991.html