P2325 [SCOI2005]王室联邦
传送门
题意:
将一棵树分为大小在\([B,3B]\)之间的块,每个块有一个编号\(i\),每种块\(i\)有一个关键点\(P\)(关键点\(P\)可以不再块\(i\)中),所有编号为\(i\)的块内的所有点到\(P\)的路径上不存在其他编号不为\(i\)的块内的点(除关键点\(P\))。其中,\(P\)可以为多种编号的块的关键点。问将这棵树划分的方案。
思路:
树分块。
对树进行DFS,用一个栈保存遍历到的节点,记录对\(u\)的子树\(v\)DFS前后top的差值\(\Delta\),若\(\Delta \geq B\),则将栈中新加入的\(\Delta\)个元素分为同一个块,然后令\(u\)为这个块的关键点。最后DFS结束后将栈内剩余的节点加入最后一个块中。这样分块能够满足题目的所有条件。
关于算法正确性:每次的\(\Delta\)在\([B,2B]\)之间,而最后剩余的栈内元素一定在\([0,B)\)之间,所以最后一个块的大小不会超过\([2B,3B)\)个,保证合法。
AC Code:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1000+100;
int n,b;
struct node{
int to,nxt;
}e[N<<1];
int head[N],tot;
void add(int u,int v){
e[++tot].to=v;e[tot].nxt=head[u];head[u]=tot;
}
int s[N],top;
int col[N],sh[N],cnt;
void dfs(int u,int fa){
int now=top;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue ;
dfs(v,u);
if(top-now>=b) {
sh[++cnt]=u;
while(top!=now) col[s[top--]]=cnt;
}
}
s[++top]=u;
}
int main(){
scanf("%d%d",&n,&b);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,0);
while(top) col[s[top--]]=cnt;
printf("%d\n",cnt);
for(int i=1;i<=n;i++) printf("%d%c",col[i],i==n?'\n':' ');
for(int i=1;i<=cnt;i++) printf("%d%c",sh[i],i==n?'\n':' ');
return 0;
}