首页 > 代码库 > BZOJ 1086 SCOI2005 王室联邦 块状树

BZOJ 1086 SCOI2005 王室联邦 块状树

题目大意:给定一棵树,要求将这棵树分成一些块,使每块大小在[B,3B]之间

《手把手教你块状树系列》

- -终于搞懂这题怎么做了

- -去网上扒了个代码居然是错的 坑死我了

- -还好题解的思想是对的

朴素的分块方式是贪心 能加就加 这种方法存在着严重的效率问题 可以被菊花卡成O(n)块

因此我们可以为其它的块预留位置 如果一块大小刚好>=b 就将这坨东西分成一块

首先任选一点开始深搜 维护一个栈 每个点退出递归时压栈 自下至上进行合并

如果某棵子树深搜完之后栈内元素数>=b 就把当前的栈内元素合并为一个块

但是这种方法存在一个问题 就是如果某棵子树深搜之后不到b 去深搜下一个子树 可能在下一个子树内部的某个位置超过b

这样会导致分成的块不连通

因此我们在每次进入递归时维护一个栈底,对于当前子树来说这个栈底就是整个栈的底,栈底以下的元素不能修改或弹栈

这样当一棵子树深搜过后由于子树内未分块节点不超过b,之前搜过的未分块节点数也不超过b,因此每块不超过2b

那么题目为什么给了3b呢?

深搜结束后可能会剩余一些节点,这些节点的数量不超过b,而且一定与当前分出的最后一块连通

因此我们将剩余节点分到最后一块中,可以保证最后一块的大小不超过3b

一遍深搜即可出解。

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 1010
using namespace std;
struct abcd{
	int to,next;
}table[M<<1];
int head[M],tot;
int n,b,cnt;
int belong[M],root[M],stack[M],top;
void Add(int x,int y)
{
	table[++tot].to=y;
	table[tot].next=head[x];
	head[x]=tot;
}
void DFS(int x,int from)
{
	int i,bottom=top;
	for(i=head[x];i;i=table[i].next)
		if(table[i].to!=from)
		{
			DFS(table[i].to,x);
			if(top-bottom>=b)
			{
				root[++cnt]=x;
				while(top!=bottom)
					belong[stack[top--]]=cnt;
			}
		}
	stack[++top]=x;
}
int main()
{
	int i,x,y;
	cin>>n>>b;
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		Add(x,y);Add(y,x);
	}
	DFS(1,0);
	while(top)
		belong[stack[top--]]=cnt;
	cout<<cnt<<endl;
	for(i=1;i<=n;i++)
		printf("%d%c",belong[i],i==n?'\n':' ');
	for(i=1;i<=cnt;i++)
		printf("%d%c",root[i],i==cnt?'\n':' ');
	return 0;
}


BZOJ 1086 SCOI2005 王室联邦 块状树