BZOJ 1086: [SCOI2005]王室联邦 树分块,贪心DFS

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

解题思路: 看到这题就mengbier了,难道是树形DP,可是这个是输出方案怎么搞,而且3*b有什么用?啊,苦思无果,只能拜读各神牛题解了,听说是树分块的裸题???
其实我们DFS一遍就好了,对于当前一个点,如果子树大小超过B,直接把子树划分为一个省,当前根节点为省会就好了,如果子树大小小于B的话,就在当前根节点的父亲节点操作,为什么可以这么搞呢?可以参考这位同学的博客:见这里 但是我还有个疑问,按照这种做法,省会大小最大不超过2*B吧,为什么要限定3*B呢,虽然这样是不影响答案的,如果有知道的,一定要告诉小弟。。。
考虑操作。显然这需要一个栈来维护,每次回去之前将x加入栈里面。然后栈中在当前树中的节点超过b个时就将这b个放到一个省里。

代码如下:

//bzoj 1086
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2020;
int head[maxn], stk[maxn], cnt, top, idx, n, b;
int root[maxn], city[maxn];
struct edge{
    int v, nxt;
    edge(){}
    edge(int v, int nxt) : v(v), nxt(nxt) {}
}E[maxn];
void init(){
    cnt = 0;
    memset(head, -1, sizeof(head));
}
void addedge(int u, int v){
    E[cnt].v = v, E[cnt].nxt = head[u], head[u] = cnt++;
}
void dfs(int u, int fa){
    int now = top;
    for(int i = head[u]; ~i; i = E[i].nxt){
        int v = E[i].v;
        if(v == fa) continue;
        dfs(v, u);
        if(top - now >= b){
            root[++idx] = u;
            while(top != now){
                city[stk[top--]] = idx;
            }
        }
    }
    stk[++top] = u;
}
int main(){
    scanf("%d%d", &n, &b);
    init();
    for(int i = 1; i < n; i++){
        int u, v;
        scanf("%d%d", &u, &v);
        addedge(u, v);
        addedge(v, u);
    }
    dfs(1, -1);
    while(top){
        city[stk[top--]] = idx;
    }
    cout << idx << endl;
    for(int i = 1; i <= n; i++) cout << city[i] << " "; cout << endl;
    for(int i = 1; i <= idx; i++) cout << root[i] << " "; cout << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值