洛谷P2325 [SCOI2005]王室联邦(树分块)

题目

n(n<=1e3)个点的树,给定一个B(1<=B<=n)值,

要求把树分成若干个块,每一块代表一个省,大小在[B,3B]之间,

每个省必须有一个省会,这个省会可以位于省内,也可以在该省外。

但是该省的任意一个城市到达省会所经过的道路上的城市(除了最后一个城市,即该省省会)都必须属于该省。

一个城市可以作为多个省的省会。

要求输出块数,每个点属于的块号,每个块的省会。

思路来源

https://www.luogu.com.cn/problem/solution/P2325

题解

省会的要求,至少与一个省内的一个点相连。

感觉看了题解之后,是先刻意这么构造,才有的这道题……

先dfs,对于u的儿子v,

如果存在几个儿子v回溯大小之和>=B,则将其分为一块,

考虑u的第一个儿子v的sz[v]=B-1,回溯到u时不会处理,

第二个儿子v的sz[v]=B,于是回溯到u处时会被统计,即块sz<=2*B-1<3B

 

而这样分,最后根节点1的一些子树v之和sz<B,此时加上根节点1,总sz<=B,

如果sz=B,可以将其新分一块;

如果sz=B-1,可以将其与<=2*B-1的上一块合并,因为省会相同

事实上,注意到2*B-1+B=3*B-1<3*B,

所以,直接将这一小块与刚才最后合法的一大块合并即可,

特判这一小块是第一块的情形,

由于B<=n,故一定有解

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
#define pb push_back
int n,b,u,v;
int dfn[N],c;
int cnt,rt[N],bel[N];
vector<int>e[N];
void dfs(int u,int fa){
    int now=c;
    for(int i=0;i<e[u].size();++i){
        int v=e[u][i];
        if(v==fa)continue;
        dfs(v,u);
        //若干个v子树的大小的和
        if(c-now>=b){
            rt[++cnt]=u;
            for(;c>now;--c){
                bel[dfn[c]]=cnt;
            }
        }
    }
    dfn[++c]=u;//后序
}
int main(){
    scanf("%d%d",&n,&b);
    for(int i=2;i<=n;++i){
        scanf("%d%d",&u,&v);
        e[u].pb(v);e[v].pb(u);
    }
    dfs(1,-1);
    if(!cnt)rt[++cnt]=1;//如果1还没有当省会 就开一个
    for(;c;--c){//把根节点1及最后的点都放入1最后的一个省会的集合里
        bel[dfn[c]]=cnt;
    }
    printf("%d\n",cnt);
    for(int i=1;i<=n;++i){
        printf("%d%c",bel[i]," \n"[i==n]);
    }
    for(int i=1;i<=cnt;++i){
        printf("%d%c",rt[i]," \n"[i==cnt]);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值