[ZJOI2007 捉迷藏] 动态点分治

题目大意

    给出一棵树,初始全为黑点。执行若干操作,操作1:将某个点的颜色取反 操作2:询问最远的两个黑点的距离。

思路

    考虑动态点分治,用可删堆维护一些链长即可,细节较多,十分累人。

    具体:先和普通点分治一样,确定树的划分方案,然后每一棵子树的重心u和上面一层的重心Fa[u]连边,构成一棵分治树T。预处理出每个点的信息dis[i][j],表示i到分治树上深度为j的祖先的实际距离。然后分治时,每个点u管辖的路径就是所有经过它的路径,考虑维护第一长和第二长(不经过同一子树),并且维护当前子树u中深度最深的点的深度(以Fa[u]为根时)Deep[u]。由于这些信息时刻会更新、删除,所以用到了可删堆。

可删堆:支持logn删除任意元素的特殊堆。实现很简单:开两个堆a和b,a存放原来堆中的元素,b用来存放待删除的元素。每一次访问堆a的最小值时,比较a.top()和b.top(),如果一样,说明a.top()已经是不存在的元素了,执行a.pop(),b.pop()。总之,核心思想就是先不忙删除,先放到b中,等到要用的时候再判断。

复杂度O(nlog^2n),有点卡常,并不是标算。BZOJ上过掉了,洛谷上80分,TLE两个点。嘛,本题就是用来练动态点分治的,超时也无所谓了。

更新的时候,在分治树上不断往上跳,更新每个祖先的相关信息,然后更新存放的答案的那个堆即可,具体细节看程序吧,说不清楚。

p.s. 一开始漏算了直接以u为端点的情况,结果还拍不出错,差点调到恍惚……(╯‵□′)╯︵┻━┻

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
#define rep(i,j,k) for (i=j;i<=k;i++)
#define down(i,j,k) for (i=j;i>=k;i--)
using namespace std;
const int N=1e5+5,K=18,INF=1e9;
int n,m,u,v,i,total,Min,root,cnt,tmp,lit[N],vis[N],size[N];
int lv[N],Fa[N],dis[N][K];
int En,fst[N],to[N*2],nxt[N*2];
char od;
struct Del_heap{
	priority_queue<int> a,b; int tot;
	int top() {
		while (!a.empty() && !b.empty() && a.top()==b.top()) a.pop(),b.pop();
		if (a.empty()) return -INF; //mistake3:原来是return 0,结果产生了不合法解:只有一个子树有黑屋,最深为a,结果拼出a+0这种不合法路径
		return a.top();
	}
	void push(int x) {
		a.push(x); tot++;
	}
	void del(int x) {
		if (!tot) return ;
		b.push(x); tot--;
	}
}ans,sc[N],Deep[N]; int fs[N]; //mistake1:Deep的意义搞错了 
void read(int &ret)
{
	char ch; ret=0;
	for (ch=getchar();ch<'0' || ch>'9';ch=getchar());
	for (;ch>='0' && ch<='9';ch=getchar()) ret=ret*10+ch-'0';
}
void add(int u,int v) {
	En++; nxt[En]=fst[u]; fst[u]=En; to[En]=v;
}
void get_size(int x)
{
	int j; vis[x]=1; size[x]=1;
	for (j=fst[x];j;j=nxt[j])
	if (!vis[to[j]]) {
		get_size(to[j]);
		size[x]+=size[to[j]];
	} vis[x]=0;
}
void get_root(int x)
{
	int j,maxsize=total-size[x]; vis[x]=1;
	for (j=fst[x];j;j=nxt[j])
	if (!vis[to[j]]) {
		get_root(to[j]);
		maxsize=max(maxsize,size[to[j]]);
	}
	if (maxsize<Min) Min=maxsize,root=x;
	vis[x]=0;
}
void get_dep(int x,int depth,int LV)
{
	int j; vis[x]++;
	dis[x][LV]=depth;
	if (LV>0) Deep[root].push(dis[x][LV-1]);
	for (j=fst[x];j;j=nxt[j])
	if (!vis[to[j]]) get_dep(to[j],depth+1,LV);
	vis[x]--;
}
void updata(int wh,int data)
{
	if (fs[wh]<=0) { //mistake4:没有特判这种情况,导致0加入了sc堆 
		fs[wh]=data; return ;
	}
	if (data>fs[wh]) {
		sc[wh].push(fs[wh]);
		fs[wh]=data;
		return ;
	}
	sc[wh].push(data);
}
void remove(int wh,int data)
{
	if (data==fs[wh]) {
		fs[wh]=sc[wh].top();
		sc[wh].del(sc[wh].top());
		return ;
	}
	sc[wh].del(data);
}
void divide(int u,int father,int LV,int &rt)
{
	get_size(u); total=size[u];
	int j,son; Min=n; get_root(u); rt=root;
	vis[rt]=1; lv[rt]=LV;
	get_dep(rt,0,LV);
	Fa[rt]=father;
	for (j=fst[rt];j;j=nxt[j])
	if (!vis[to[j]]) {
		divide(to[j],rt,LV+1,son);
		updata(rt,Deep[son].top());  //mistake2:儿子搞错了,写成了to[j] 
	}
	ans.push(fs[rt]+sc[rt].top());
	ans.push(fs[rt]);
}
void Init()
{
	read(n); cnt=n;
	rep(i,1,n-1)
	{
		read(u); read(v);
		add(u,v); add(v,u);
	}
	divide(1,0,0,tmp);
}
void change(int u)
{
	int pos=u,fa,dp;
	if (lit[u])	cnt++; else cnt--;
	lit[u]^=1;
	if (lit[u]) ans.del(fs[u]); else ans.push(fs[u]);
	while (1)
	{
		fa=Fa[pos];
		if (!fa) break;
		
		dp=dis[u][lv[pos]-1];
		ans.del(fs[fa]+sc[fa].top());
		if (!lit[fa]) ans.del(fs[fa]);
		remove(fa,Deep[pos].top());
		
		if (!lit[u]) Deep[pos].push(dp);
		else Deep[pos].del(dp);
		
		updata(fa,Deep[pos].top());
		if (!lit[fa]) ans.push(fs[fa]);
		ans.push(fs[fa]+sc[fa].top());

		pos=Fa[pos];
	}
}
void print(int x)
{
	int bn=0,i,b[10];
	if (!x) putchar('0');
	else {
		while (x>0) b[++bn]=x%10,x/=10;
		down(i,bn,1) putchar('0'+b[i]);
	}
	putchar('\n');
}
void Work()
{
	int query=0;
	read(m);
	while (m--)
	{
		od=getchar();
		if (od=='C')
		{
			read(u);
			change(u);
		}
		else {
			od=getchar();
			query++;

			if (cnt==1) print(0);
			else if (cnt==0) printf("-1\n");
			else print(ans.top());
		
		}
	}
}
int main()
{
	Init();
	Work();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Orzage/article/details/83751201