【点分树】[ZJOI2007]捉迷藏


点分树不卡常就别想过了。。。

题目

问树中最远黑点对距离
带修改

题解

点分树入门作

原图先乱跑一次点分治,保存点分治的每个root之间的父子关系,得到一颗点分树。然后我们的原图除了求dis就可以不管了

学习题解中不认识的大佬所说的套路:

  1. 点分治得到点分树
  2. 每个点用 S 1 , S 2 S_1,S_2 S1,S2两个数据结构维护,依次容斥
  3. 修改和查询都是树高(logn) × \times ×数据结构修改查询复杂度
  4. 初始化可以视为n次修改

此题用堆维护
①第一种堆:维护当前节点的每个点分树子树中的maxdis
②第二种堆:维护当前节点的所有子树到其点分树Father的dis

我们需要求的是某个节点作为中间点,它的两个点分树子树中的maxdis之和最大,也需要用一个堆来维护


技巧
此题堆需要删除堆中间部分的数,可以多保存一个trash堆用来保存要删的数,用堆的时候判一下是否两堆顶相同,相同弹掉即可

#include<bits/stdc++.h>
#define re register
using namespace std;
const int N=1e5+10,M=2*N,INF=1e9;
int n,m;
int a[N];
int head[N],nex[M],to[M],tot;
inline void build(int u,int v){tot++;nex[tot]=head[u];to[tot]=v;head[u]=tot;}
//--

namespace Cutree{
	int tp[N],sz[N],fa[N],dep[N],son[N],dfn[N];
	void dfs1(int u,int f)
	{
		sz[u]=1;dep[u]=dep[f]+1;fa[u]=f;
		for(re int i=head[u];i;i=nex[i])
		{
			int v=to[i];if(v==f)continue;
			dfs1(v,u);sz[u]+=sz[v];
			if(sz[v]>sz[son[u]])son[u]=v;
		}
	}
	void dfs2(int u,int p)
	{
		tp[u]=p;dfn[u]=1;
		if(son[u])dfs2(son[u],p);
		for(re int i=head[u];i;i=nex[i])
		{
			int v=to[i];if(dfn[v])continue;
			dfs2(v,v);
		}
	}
	inline  int lca(int x,int y)
	{
		while(tp[x]^tp[y])
		{
			if(dep[tp[x]]<dep[tp[y]])swap(x,y);
			x=fa[tp[x]];
		}
		return dep[x]<dep[y]?x:y;
	}
	inline int dis(int x,int y){return dep[x]+dep[y]-2*dep[lca(x,y)];}
}
//---
#define ap n*2+1
priority_queue<int>t[N<<1],trash[N<<1];
inline void add(int u,int w){if(w>=0)t[u].push(w);}
inline void del(int u,int w)
{
	if(w>=0&&!t[u].empty())
	{
		if(t[u].top()==w)t[u].pop();
		else	trash[u].push(w);
	}
}
inline int top(int u)
{
	while(!t[u].empty()&&!trash[u].empty()&&t[u].top()==trash[u].top())t[u].pop(),trash[u].pop();
	return !t[u].empty()?t[u].top():-INF;
}
inline int second(int u)
{
	int tmp1=top(u);
	if(tmp1==-INF)return -INF;
	t[u].pop();
	int tmp2=top(u);
	t[u].push(tmp1);
	return tmp2;
}
//---
int size,root,maxx;
int Sz[N],Fa[N];
bool vis[N];
void find(int u,int f)
{
	int tmp=0;Sz[u]=1;
	for(re int i=head[u];i;i=nex[i])
	{
		int v=to[i];if(v==f||vis[v])continue;
		find(v,u);
		Sz[u]+=Sz[v];
		tmp=max(tmp,Sz[v]);
	}
	tmp=max(tmp,size-Sz[u]);
	if(tmp<maxx)root=u,maxx=tmp;
}
void solve(int u,int f)
{
	vis[u]=1;Fa[u]=f;
	for(re int i=head[u];i;i=nex[i])
	{
		int v=to[i];if(vis[v])continue;
		size=Sz[v];maxx=INF;root=0;
		find(v,u);solve(root,u);
	}
}
inline void init()
{
	Cutree::dfs1(1,0);
	Cutree::dfs2(1,1);
	size=n;maxx=INF;root=0;
	find(1,0);solve(root,0);
}
//----
int last[N],pre;
inline void Q_add(int u)
{
	add(u,0);
	pre=top(u)+second(u);
	if(pre!=last[u])del(ap,last[u]),add(ap,last[u]=pre);
	for(re int i=u;Fa[i];i=Fa[i])
	{
		int dist=Cutree::dis(u,Fa[i]);
		int pos=top(i+n);
		add(i+n,dist);
		int sop=top(i+n);
		if(pos!=sop)del(Fa[i],pos),add(Fa[i],sop);
		
		pre=top(Fa[i])+second(Fa[i]);
		if(pre!=last[Fa[i]])del(ap,last[Fa[i]]),add(ap,last[Fa[i]]=pre);
	}
}
inline void Q_del(int u)
{
	del(u,0);
	pre=top(u)+second(u);
	if(pre!=last[u])del(ap,last[u]),add(ap,last[u]=pre);
	for(re int i=u;Fa[i];i=Fa[i])
	{
		int dist=Cutree::dis(u,Fa[i]);
		int pos=top(i+n);
		del(i+n,dist);
		int sop=top(i+n);
		if(pos!=sop)del(Fa[i],pos),add(Fa[i],sop);
		
		pre=top(Fa[i])+second(Fa[i]);
		if(pre!=last[Fa[i]])del(ap,last[Fa[i]]),add(ap,last[Fa[i]]=pre);
	}
}
char s[10];
int main()
{
	scanf("%d",&n);
	for(re int x,y,i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		build(x,y);build(y,x);
	}
	init();
	for(int i=1;i<=n;i++)Q_add(i);
	int g=n;
	scanf("%d",&m);
	while(m--)
	{
		scanf("%s",s);
		if(s[0]=='C')
		{
			int x;scanf("%d",&x);
			if(a[x]==0)Q_del(x),a[x]=1,g--;
			else	   Q_add(x),a[x]=0,g++;
		}
		else
		{
			if(g==0)printf("-1\n");
			else if(g==1)printf("0\n");
			else printf("%d\n",top(ap));
		}
	}
}

最后当然是加火车头过的啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值