1086: [SCOI2005]王室联邦
http://www.lydsy.com/JudgeOnline/problem.php?id=1086
Time Limit: 10 Sec Memory Limit: 162 MBSec Special JudgeDescription
“余”人国的国王想重新编制他的国家。他想把他的国家划分成若干个省,每个省都由他们王室联邦的一个成
员来管理。他的国家有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
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
2 1 1 3 3 3 3 2
2 1 8
dfs+分块
讲课的时候老师专门用这道题命名了一种分块方式——王室联邦分块
个人理解:
一边dfs,一边分块
对与以x为根的子树来说,如果它的大小满足了要求,就把子树除去根节点(即除去x)分为一块
为什么要除去x?后面说
不满足要求,它的大小累计至它的父节点(可以理解为分入等待区,等待被划分为一块),与兄弟节点的子树合并,直至满足大小,划分为一块
若一直不满足大小,就一直累计上去,直至根节点
最后累计至根节点的怎么办?
那本题来说,这题[B,3B]给的非常巧
首先一直向下递归至最底层、自底层向上合并,
以x为根子树大小<B的累积至父节点,与兄弟节点子树合并为一个子树
>=B,除去x划分出一块
因为是自底层向上,所以保证不会有子树大小>=2B
因为在>=2B之前,一定会先>=B,划分为一块
若想合并为一块,则子树满足两个条件:
1、两子树大小均<B,否则早就划分为一块
2、两子树大小相加>=B
同时,我们可以推出,合并划分出的一块大小<=2B
所以,对于最后累积至根节点的值,合并到离根节点最近的一块里就行
现在说说为什么要除去子树的根节点?
假设x有3个子树1、2、3
1、2合并可划分为一块,3不能自己划分为一块
那么应该1、2合并,归为一块,3向上累积至x
如果划分1、2为一块时不除去x,也就是1、2、x为一块
那么3就无法通过父节点x与上面联通
1、用栈辅助输出城市为哪个省
View Code
View Code
#include<cstdio> #define N 1001 using namespace std; int n,b,tot,stack[N],top,bl[N],now; int to[N*2],next[N*2],front[N],sum[N],id[N],cap[N]; bool v[N]; void add(int u,int v) { to[++tot]=v;next[tot]=front[u];front[u]=tot; to[++tot]=u;next[tot]=front[v];front[v]=tot; } void dfs(int x,int fa) { stack[++top]=x; for(int i=front[x];i;i=next[i]) { if(to[i]==fa) continue; dfs(to[i],x); if(sum[x]+sum[to[i]]>=b) { cap[++now]=x; sum[x]=0; while(stack[top]!=x) bl[stack[top--]]=now; } else sum[x]+=sum[to[i]]; } sum[x]++; } int main() { scanf("%d%d",&n,&b); if(n<b) { printf("0"); return 0; } int x,y; for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); } dfs(1,0); while(top) bl[stack[top--]]=now; printf("%d ",now); for(int i=1;i<=n;i++) printf("%d ",bl[i]); printf(" "); for(int i=1;i<=now;i++) printf("%d ",cap[i]); }
2、递归标记城市为哪个省
#include<cstdio> #define N 1001 using namespace std; int n,b,tot; int to[N*2],next[N*2],front[N],sum[N],id[N],cap[N],now; bool v[N]; void add(int u,int v) { to[++tot]=v;next[tot]=front[u];front[u]=tot; to[++tot]=u;next[tot]=front[v];front[v]=tot; } void mark(int x,int fa) { for(int i=front[x];i;i=next[i]) if(to[i]!=fa&&!id[to[i]]&&v[to[i]]) { id[to[i]]=now;mark(to[i],x); } } void dfs(int x,int fa) { v[x]=true; for(int i=front[x];i;i=next[i]) { if(to[i]==fa) continue; dfs(to[i],x); sum[x]+=sum[to[i]]; if(sum[x]>=b) { now++; mark(x,fa); cap[now]=x; sum[x]=0; } } sum[x]++; } int main() { scanf("%d%d",&n,&b); int x,y; for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); } dfs(1,0); for(int i=1;i<=n;i++) if(!id[i]) id[i]=now; printf("%d ",now); for(int i=1;i<=n;i++) printf("%d ",id[i]); printf(" "); for(int i=1;i<=now;i++) printf("%d ",cap[i]); }