1086: [SCOI2005]王室联邦
Time Limit: 10 Sec Memory Limit: 162 MBSec Special Judge
Submit: 1107 Solved: 662
Description
“余”人国的国王想重新编制他的国家。他想把他的国家划分成若干个省,每个省都由他们王室联邦的一个成
员来管理。他的国家有n个城市,编号为1..n。一些城市之间有道路相连,任意两个不同的城市之间有且仅有一条
直接或间接的道路。为了防止管理太过分散,每个省至少要有B个城市,为了能有效的管理,每个省最多只有3B个
城市。每个省必须有一个省会,这个省会可以位于省内,也可以在该省外。但是该省的任意一个城市到达省会所经
过的道路上的城市(除了最后一个城市,即该省省会)都必须属于该省。一个城市可以作为多个省的省会。聪明的
你快帮帮这个国王吧!
Input
第一行包含两个数N,B(1<=N<=1000, 1 <= B <= N)。接下来N-1行,每行描述一条边,包含两个数,即这
条边连接的两个城市的编号。
Output
如果无法满足国王的要求,输出0。否则输出数K,表示你给出的划分方案中省的个数,编号为1..K。第二行输
出N个数,第I个数表示编号为I的城市属于的省的编号,第三行输出K个数,表示这K个省的省会的城市编号,如果
有多种方案,你可以输出任意一种。
Sample Input
8 2
1 2
2 3
1 8
8 7
8 6
4 6
6 5
Sample Output
3
2 1 1 3 3 3 3 2
2 1 8
HINT
Source
题解:树上分块。
转载自:http://blog.csdn.net/popoqqq/article/details/42772237
朴素的分块方式是贪心 能加就加 这种方法存在着严重的效率问题 可以被菊花卡成O(n)块
因此我们可以为其它的块预留位置 如果一块大小刚好>=b 就将这坨东西分成一块
首先任选一点开始深搜 维护一个栈 每个点退出递归时压栈 自下至上进行合并
如果某棵子树深搜完之后栈内元素数>=b 就把当前的栈内元素合并为一个块
但是这种方法存在一个问题 就是如果某棵子树深搜之后不到b 去深搜下一个子树 可能在下一个子树内部的某个位置超过b
这样会导致分成的块不连通
因此我们在每次进入递归时维护一个栈底,对于当前子树来说这个栈底就是整个栈的底,栈底以下的元素不能修改或弹栈
这样当一棵子树深搜过后由于子树内未分块节点不超过b,之前搜过的未分块节点数也不超过b,因此每块不超过2b
那么题目为什么给了3b呢?
深搜结束后可能会剩余一些节点,这些节点的数量不超过b,而且一定与当前分出的最后一块连通
因此我们将剩余节点分到最后一块中,可以保证最后一块的大小不超过3b
一遍深搜即可出解。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define N 20000 using namespace std; int n,m; int point[N],next[N],v[N],top,cnt,tot; int belong[N],st[N],root[N]; void add(int x,int y) { tot++; next[tot]=point[x]; point[x]=tot; v[tot]=y; tot++; next[tot]=point[y]; point[y]=tot; v[tot]=x; } void dfs(int x,int fa) { int lower=top; for(int i=point[x];i;i=next[i]) if (v[i]!=fa) { dfs(v[i],x); if (top-lower>=m) { root[++cnt]=x; while (top!=lower) belong[st[top--]]=cnt; } } st[++top]=x; } int main() { scanf("%d%d",&n,&m); for (int i=1;i<n;i++) { int x,y; scanf("%d%d",&x,&y); add(x,y); } dfs(1,0); while (top) belong[st[top--]]=cnt; printf("%d\n",cnt); for (int i=1;i<=n;i++) printf("%d ",belong[i]); printf("\n"); for (int i=1;i<=cnt;i++) printf("%d ",root[i]); printf("\n"); }