首页 > 其他分享 >【ZJOI2007】捉迷藏(动态树分治)

【ZJOI2007】捉迷藏(动态树分治)

时间:2022-10-31 07:58:54浏览次数:65  
标签:q1 q2 fa 捉迷藏 分治 int ZJOI2007 now size

显然只有一次询问的话,可以用点分治来实现。

但是现在我们有多组询问,还带有修改,我们只能通过动态点分治来做了。

动态点分治的主要思想:省去每次点分治求重心的过程,直接预处理出来(因为树的形态不会改变),建立点分树。那么我们每次分治时只需按照点分树上的路径走就是了。

例如,对于这么一颗树:(样例,1为根)(感谢绘图网站https://csacademy.com/app/graph_editor/

在这里插入图片描述

建出来的点分树是这样的:(3为根)

在这里插入图片描述
注意:点分树只是把在原树中点分治遍历的顺序建了出来,如果求 \(dis\) 或 \(lca\) 还是要在原树中求,不能在点分树上求,因为点分树改变了原树形态。所以如果题目的询问不是针对全局的,而是带有父子关系的(比如多次询问以 \(u\) 为根的子树中路径长度为 \(k\) 的路径个数)就不能建点分树了。

应该不能吧,不知道有没有一种玄学的方法

然后我们对于每个点,维护四个大根堆:

  1. \(dis1\),维护:在点分树中以 \(u\) 为根的子树中,所有灭灯的节点到 \(u\) 的 \(fa\) 的距离。
  2. \(erase1\),因为我们有时要从 \(dis1\) 中删去一些值,所以 \(erase1\) 维护在 \(dis1\) 中要删去的值。
  3. \(dis2\),维护:在点分树中 \(u\) 的所有儿子的 \(dis1\) 的堆顶。那么将 \(dis2\) 的 \(top1\) 和 \(top2\) 取出来,再相加,就是合法的经过 \(u\) 的最长路径的长度。
  4. \(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

相关文章

  • 【XSY4746】会议选址(三度化,边分治,点分治)
    这种关键点的重心问题,一般有两种思路。一种是合并:对于两个不交的点集\(S,T\),\(S\cupT\)的重心必在\(S,T\)重心间的路径上,二分即可,需要数据结构支持dfn区间内\(S\c......
  • 【XSY3979】数据结构(分治,剪枝)
    题面数据结构题解挺神奇的一道题。正解是对\(y\)坐标分治。每次考虑\(y\)坐标在\([l,mid]\)范围内的红点和\(y\)坐标在\([mid+1,r]\)范围内的蓝点匹配成点......
  • 【XSY3972】树与图(树形dp,树剖,分治NTT)
    题面树与图题解不难发现本题可以转化成以下题目:给定一个\(n\)个点的有根树,你可以在树上选择\(k\)个点,满足对于任意两个点都不互为祖先关系,且从根到每个叶子的路......
  • 【XSY3971】不难题(点分治)
    题面不难题题解百年未有之写点分……好久没写了,也当复习了一遍吧。对于树上的一个扫描半径为\(d\)的在\(u\)节点的雷达,我们将其所能覆盖到的点的集合称作“圆\(......
  • P2272 [ZJOI2007]最大半连通子图
    哎,这道题打了半个小时,调了两个小时,最后发现竟然是把\(Tarjan\)里\(while\)给打成\(if\),呜呜,枉费我两个小时时间,所以下次一定要记住不能打成\(if\)(估计也就我一个......
  • 【XSY3633】匹配(树形 DP,树链剖分,分治)
    考虑最普通的DP:设\(f_{u,i,0/1}\)表示\(u\)子树内恰好包含\(i\)条边的最大权匹配,其中\(u\)有无被匹配。这是个树上背包,暴力DP是\(O(n^2)\)的。可以发现\(f......
  • 【XSY3535】购物(决策单调性优化DP,分治,结论,背包)
    题面购物题解决策单调性全忘了……先考虑暴力怎么做,我们可以设\(f_{i,j}\)表示前\(i\)个商店买了\(j\)件物品的最小代价,然后有转移:\[f_{i,j}=\min_{k=0}^j(f_{i......
  • 【XSY3241】暴风士兵(stormtrooper)(多项式分治,期望)
    设一个人被扣了\(i\)滴血的概率为\(p_i\),设\(c_i=exp-i\)且只有\(c_0,c_1,\cdots,c_{exp}\)有值,那么题目就是在问\(\sum\limits_{i=0}^{exp}c_ip_i\)。我们设\(p......
  • 【XSY3338】game(期望,点分治,FFT)
    题面game题解首先可以看出“等概率选连通块->连通块内等概率选点”相当于“全局等概率选点”。一开始感觉无从下手,但是题目中还是给了一点提示。题目让我们输出答......
  • 【XSY2423】跳蚤(根号分治)
    题面题解神奇的分类讨论。首先注意到每次所有跳蚤都只会往右跳,也就是说只要某一只跳蚤跳出了\(\max(r_i)\)它就不会再有贡献了。(和火神的鱼类似)令\(R=\max(r_i)......