P2325 [SCOI2005]王室联邦 - 树分块学习小记

https://www.luogu.com.cn/problem/P2325

据说最常用的树分块是由这题得来的。

分块之后可以得到:每块大小大于等于\(B\),小于\(3B\)(实际上至多一个大于等于\(2B\))。并且对于每个块,都存在一个点(记为块顶,它不一定在块内),使得这个块的点集并这个点形成的点集联通。

维护一个栈\(st\),设栈顶位置为\(cur\),dfs的时候到每个点记下到达这个点时候栈顶的位置\(bef\)

过程:

  1. 遍历儿子\(y\),回溯到\(x\)。如果当前新加进来的大于等于\(B\)\(cur-bef\ge B\)),则将\([bef+1,cur]\)中的全都分成一块。
  2. \(x\)要回溯,把\(x\)加入栈顶。
  3. dfs完之后,如果栈还有剩余,就把剩下的加入最后一个块中。

分析一下:进入子树前,栈中点最多是\(B-1\)。从子树出来,栈中点最多是\(B\)。于是总共最多为\(2B-1\)。栈中若有剩余最多为\(B-1\),加入最后一个块最多为\(3B-1\)


using namespace std;
#include <bits/stdc++.h>
#define N 1005
int n,B;
struct EDGE{
	int to;
	EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
void link(int u,int v){
	e[ne]={v,last[u]};
	last[u]=e+ne++;
}
int st[N],cur;
int bel[N],c[N],k;
void dfs(int x,int fa){
	int tmp=cur;
	for (EDGE *ei=last[x];ei;ei=ei->las)
		if (ei->to!=fa){
			dfs(ei->to,x);
			if (cur-tmp>=B){
				++k;
				while (cur>tmp)
					bel[st[cur--]]=k;
				c[k]=x;
			}
		}
	st[++cur]=x;
}
int main(){
	scanf("%d%d",&n,&B);
	for (int i=1;i<n;++i){
		int u,v;
		scanf("%d%d",&u,&v);
		link(u,v),link(v,u);
	}
	dfs(1,0);
	while (cur)
		bel[st[cur--]]=k;
	c[k]=1;
	printf("%d\n",k);
	for (int i=1;i<=n;++i)
		printf("%d ",bel[i]);
	printf("\n");
	for (int i=1;i<=k;++i)
		printf("%d ",c[i]);
	printf("\n");
	return 0;
}
posted @ 2021-03-09 16:33  jz_597  阅读(87)  评论(0编辑  收藏  举报