[ZJOI2007]Hide 捉迷藏

<body> <center><h1>1095: [ZJOI2007]Hide 捉迷藏</h1><span class="green">Time Limit: </span>40 Sec&nbsp;&nbsp;<span class="green">Memory Limit: </span>256 MB<br><span class="green">Submit: </span>5824&nbsp;&nbsp;<span class="green">Solved: </span>2505<br>[<a href="submitpage.php?id=1095">Submit</a>][<a href="problemstatus.php?id=1095">Status</a>][<a href="bbs.php?id=1095">Discuss</a>]</center><h2>Description</h2><div class="content"><p>  捉迷藏 Jiajia和Wind是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind和孩子们决定在家里玩<br> 捉迷藏游戏。他们的家很大且构造很奇特,由N个屋子和N-1条双向走廊组成,这N-1条走廊的分布使得任意两个屋<br> 子都互相可达。游戏是这样进行的,孩子们负责躲藏,Jiajia负责找,而Wind负责操纵这N个屋子的灯。在起初的<br> 时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要<br> 求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia希望知道可能的最远的两<br> 个孩子的距离(即最远的两个关灯房间的距离)。 我们将以如下形式定义每一种操作: C(hange) i 改变第i个房<br> 间的照明状态,若原来打开,则关闭;若原来关闭,则打开。 G(ame) 开始一次游戏,查询最远的两个关灯房间的<br> 距离。</p></div><h2>Input</h2><div class="content"><p>  第一行包含一个整数N,表示房间的个数,房间将被编号为1,2,3…N的整数。接下来N-1行每行两个整数a, b,<br> 表示房间a与房间b之间有一条走廊相连。接下来一行包含一个整数Q,表示操作次数。接着Q行,每行一个操作,如<br> 上文所示。</p></div><h2>Output</h2><div class="content"><p>  对于每一个操作Game,输出一个非负整数到hide.out,表示最远的两个关灯房间的距离。若只有一个房间是关<br> 着灯的,输出0;若所有房间的灯都开着,输出-1。</p></div><h2>Sample Input</h2> <div class="content"><span class="sampledata">8<br> 1 2<br> 2 3<br> 3 4<br> 3 5<br> 3 6<br> 6 7<br> 6 8<br> 7<br> G<br> C 1<br> G<br> C 2<br> G<br> C 1<br> G</span></div><h2>Sample Output</h2> <div class="content"><span class="sampledata">4<br> 3<br> 3<br> 4</span></div><h2>HINT</h2> <div class="content"><p></p><p>对于100%的数据, N ≤100000, M ≤500000。</p><p></p></div><h2>Source</h2> <div class="content"><p><a href="problemset.php?search="></a></p></div><center>[<a href="submitpage.php?id=1095">Submit</a>][<a href="problemstatus.php?id=1095">Status</a>][<a href="bbs.php?id=1095">Discuss</a>]</center><br> <a href="./"><span class="red">HOME</span></a> <a href="javascript:history.go(-1)"><span class="red">Back</span></a> </body>

给定一棵树,一开始每个点都是黑点,多次改变某个点的状态或询问距离最远的两个黑点的距离

分析

https://blog.csdn.net/popoqqq/article/details/44461423

动态点分治题,hzwer写错了,看了好久才发现。

我们把分治过程中遍历过的重心都连起来,上一层的重心链接下一层的重心,可以得到一棵新的树,叫做点分树。下面我们开始讨论这棵新树。

显然这棵树的高度不会超过\(O(\log n)\)

然后我们每个节点开两个堆

  • 第一个堆记录子树中所有节点到点分树上父亲节点的距离
  • 第二个堆记录所有子节点的堆顶

那么一个节点的堆2中的最大和次大加起来就是子树中经过这个节点的最长链。

然后我们最后开一个全局的堆,记录所有堆2中最大值和次大值之和。那么全局的堆顶就是答案。

时间复杂度\(O((n+m)\log^2 n)\)

co int N=1e5+1;
int n,m;
vector<int> e[N];
int bin[20],lg[2*N],mn[18][N*2],dep[N],pos[N],dfn;
void dfs(int x,int fa){
	mn[0][pos[x]=++dfn]=dep[x];
	for(int i=0,y;i<e[x].size();++i){
		if((y=e[x][i])==fa) continue;
		dep[y]=dep[x]+1,dfs(y,x);
		mn[0][++dfn]=dep[x];
	}
}
il int rmq(int x,int y){
	x=pos[x],y=pos[y];
	if(x>y) swap(x,y);
	int t=lg[y-x+1];
	return min(mn[t][x],mn[t][y-bin[t]+1]);
}
il int dis(int x,int y){
	return dep[x]+dep[y]-2*rmq(x,y);
}

int root,sum,size[N],f[N],vis[N],fa[N];
void get_root(int x,int fa){
	size[x]=1,f[x]=0;
	for(int i=0,y;i<e[x].size();++i){
		if((y=e[x][i])==fa||vis[y]) continue;
		get_root(y,x);
		size[x]+=size[y],f[x]=max(f[x],size[y]);
	}
	f[x]=max(f[x],sum-size[x]);
	if(f[x]<f[root]) root=x;
}
void divide(int x,int f){
	fa[x]=f,vis[x]=1;
	for(int i=0,y;i<e[x].size();++i){
		if(vis[y=e[x][i]]) continue;
		root=0,sum=size[y],get_root(y,x),divide(root,x);
	}
}

struct heap{
	priority_queue<int> A,B;
	il void push(int x) {A.push(x);}
	il void erase(int x) {B.push(x);}
	il void pop(){
		while(B.size()&&A.top()==B.top()) A.pop(),B.pop();
		A.pop();
	}
	il int top(){
		while(B.size()&&A.top()==B.top()) A.pop(),B.pop();
		if(!A.size()) return 0;
		return A.top();
	}
	il int size() {return A.size()-B.size();}
	il int stop(){ // second top
		if(size()<2) return 0;
		int x=top();pop();
		int y=top();push(x);
		return y;
	}
}A,B[N],C[N];
int tot,clo[N];
void turn_off(int u,int v){
	if(u==v){
		B[u].push(0);
		if(B[u].size()==2)A.push(B[u].top());
	}
	if(!fa[u])return;
    int f=fa[u],D=dis(f,v),tmp=C[u].top();
	C[u].push(D);
	if(D>tmp){
		int mx=B[f].top()+B[f].stop(),size=B[f].size();
		if(tmp)B[f].erase(tmp);
		B[f].push(D);
		int now=B[f].top()+B[f].stop();
		if(now>mx){
			if(size>=2)A.erase(mx);
			if(B[f].size()>=2)A.push(now);
		}
	}
	turn_off(f,v);
}
void turn_on(int u,int v){
	if(u==v){
		if(B[u].size()==2)A.erase(B[u].top());
		B[u].erase(0);
	}
	if(!fa[u])return;
	int f=fa[u],D=dis(f,v),tmp=C[u].top();
	C[u].erase(D);
	if(D==tmp){
		int mx=B[f].top()+B[f].stop(),size=B[f].size();
		B[f].erase(D);
		if(C[u].top())B[f].push(C[u].top());
		int now=B[f].top()+B[f].stop();
		if(now<mx){
			if(size>=2)A.erase(mx);
			if(B[f].size()>=2)A.push(now);
		}
	}
	turn_on(f,v);
}

int main(){
	read(n);
	for(int i=1,u,v;i<n;++i){
		read(u),read(v);
		e[u].push_back(v),e[v].push_back(u);
	}
	// Range Minimum Query
	bin[0]=1;for(int i=1;i<20;++i) bin[i]=bin[i-1]<<1;
	lg[0]=-1;for(int i=1;i<n<<1;++i) lg[i]=lg[i>>1]+1;
	dfs(1,0);
	for(int i=1;i<=lg[dfn];++i)
		for(int j=1,t=dfn-bin[i]+1;j<=t;++j)
			mn[i][j]=min(mn[i-1][j],mn[i-1][j+bin[i-1]]);
	// Point Divide and Conquer
	root=0,f[0]=n,sum=n,get_root(1,0),divide(root,0);
	for(int i=1;i<=n;++i) C[i].push(0),clo[i]=1,turn_off(i,i),++tot;
	for(read(m);m--;){
		char ch[2];scanf("%s",ch);
		if(ch[0]=='G'){
			if(tot<=1) printf("%d\n",tot-1);
			else printf("%d\n",A.top());
		}
		else{
			int x=read<int>();
			if(clo[x]) turn_on(x,x),--tot;
			else turn_off(x,x),++tot;
			clo[x]^=1;
		}
	}
	return 0;
}

posted on 2019-04-10 16:39  autoint  阅读(244)  评论(0编辑  收藏  举报

导航