[SCOI2005]王室联邦

1086: [SCOI2005]王室联邦

http://www.lydsy.com/JudgeOnline/problem.php?id=1086

Time Limit: 10 Sec  Memory Limit: 162 MBSec  Special Judge

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
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、用栈辅助输出城市为哪个省
#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]);
}
View Code

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]);
}
View Code
 
 
原文地址:https://www.cnblogs.com/TheRoadToTheGold/p/6556222.html