Codeforces Round #364 (Div. 1)-树的带权重心(模板)||贪心-Connecting Universities

http://codeforces.com/contest/700/problem/B
//


11.27:亚洲赛前,又看了一下这个题,当时是为了补树的重心而看的。有一点东西遗漏了。那就是为什么一开始就选了树的重心。
因为 我们可以把树上的这个分成两个集合,然后两边的点的数目相等。符合这个条件的就是树的重心,所以我们只需要找到树的重心就行啦。
然后下面我们证明了 树的重心满足的性质,那就是让每个边都取了min(min为其左右的顶点数的小的那个)。然后我们又创新奇迹的用了两个搜索来完成了这个玩意。嗯,开心qwq

定义
树的重心:删去重心后,生成的多棵树尽可能平衡。即以这个点为根,那么所有的子树(不算整个树自身)的大小都不超过整个树大小的一半。
性质
性质 1 :树中所有点到某个点的距离和中,到重心的距离和是最小的,如果有两个距离和,他们的距离和一样。
性质 2 :把两棵树通过某一点相连得到一颗新的树,新的树的重心必然在连接原来两棵树重心的路径上。
性质 3 :一棵树添加或者删除一个节点,树的重心最多只移动一条边的位置。
!树的重心做,这样每条边都能取到min,因为他的左侧再大, 也不过是一条分支,而这个分支始终是小于k/2的。内部那就更不用说了
好题,真好题
PS :带权重心和重心的差别就是 重心是数量的重心,而带权的是根据每个点被赋予的不同的权重。像这道题那些没有大学的城市我们给他的权重是0!
给定你一个m个村庄。还有k个大学,
然后给你各个大学所在村庄的位置。
和m-1条各村庄之间的路线,将k各大学两两相连。
问你如何连接,能够让总路线最长(默认边权都是1)
第一种策略是树的重心。
若想最多,每条边应该得到min min是两边 最小的那个大学数量。
我开始完全不理解,树的重心明明是各带权点距离重心距离和最小。
而题中要求的是最大的。这怎么会有关系
因为这个性质要求的是一群路,什么路呢
就是所有带权点(这道题是带权重心)和重心之间的连接和最小
而这种路一定能满足 每条边取min.!!!!!
所以我们就可以先求 一个树的带权重心,然后就求每个带权点距离
重心的距离就行了。(这样的连接过程说明配对的过程都是要经过树的重心的)
这里写图片描述
我们可以发现,右侧一个子树中每个边都达到了min。
所以取树的重心作为一个终点,能够让他们都达到min,(每个带权点做一条对应重心的线)然后我们求总边权的时候,只需要沿着 数的重心,求出所有距离和就ok了,因为这样都能每个边取到min, 就是最小(蓝色的也能去掉min啊,如果不是这样的话,蓝色的就不一定能取到min)

或者说,根据贪心的思路,
我们不求树的带权重心了(为啥要求)
只需要贪心的求出每个边的 min,直接加起来就行了
不过求带权重心是为了说明 这种情况是能够成立的。
所以呢,我们直接用dfs,根据dfs序求出dfs方向的i点为根的siz,然后求min。
再遍历的时候,,每条边把min加起来就行。(并且弄一个反向的树)
这个比较方便,简单。

#include<bits/stdc++.h>//树的重心版本
using namespace std;
const int maxn=4e5+20;
struct Node{
       int to;
       int next;
}node[maxn];
int len;
int head[maxn];
void add(int a,int b){
     node[len].to=b;
     node[len].next=head[a];
     head[a]=len++;
}
int m;
bool vis[maxn];
int c_tre[maxn];
int num[maxn];
bool visit[maxn];//对应权重点
int k;
long long all=0;
int bkbk;
void Init(){
len=0;
memset(head,-1,sizeof(head));
memset(vis,false,sizeof(vis));
memset(c_tre,0,sizeof(c_tre));
memset(visit,false,sizeof(visit));
memset(num,0,sizeof(num));
all=0;
bkbk=0;
}
void dfs1(int x,int pre){
    if(visit[x]) {num[x]=1;}
    for(int i=head[x];i!=-1;i=node[i].next){
        int to=node[i].to;
        if(to==pre)continue;
        dfs1(to,x);
        num[x]+=num[to];
    }
    return ;//返回的这个num 带自身。
}
void dfs(int u,int fa)//求树的带权重心模板。
{
    int mx=k*2-num[u];
    for(int i=head[u]; i!=-1; i=node[i].next)
    {
        int v=node[i].to;
        if(v==fa) continue;
        mx=max(mx,num[v]);
    }
    if(mx<=k){
        bkbk=u;
        return ;
    }
    for(int i=head[u]; i!=-1; i=node[i].next)
    {
        int v=node[i].to;
        if(v==fa) continue ;
        dfs(v,u);
        if(bkbk) return ;
    }
}
void  dfs3(int c,int len){//这个搜索的过程是 累加求每个点距离重心的距离
     vis[c]=true;
     if(visit[c]) all+=len;
     for(int i=head[c];i!=-1;i=node[i].next){
          int to=node[i].to;
          if(vis[to]) continue;
          dfs3(to,len+1);
     }
}
int main()
{   int t;
    int a,b;
    scanf("%d%d",&m,&k);
          Init();
          for(int i=0;i<2*k;i++){
             scanf("%d",&a);
             visit[a]=true;
          }
          //k*=2;
          for(int i=0;i<m-1;i++){
              scanf("%d%d",&a,&b);
              add(b,a);
              add(a,b);
          }
          dfs1(1,-1);//记录一下权重.
          dfs(1,-1);//计算一下带权重心。
          all=0;
          memset(vis,false,sizeof(vis));
          dfs3(bkbk,0);//计算一下各个点距离 重心的权重和。
          cout<<all<<endl;
    return 0;
}

贪心版本

#include<bits/stdc++.h>
using namespace std;
/* 第一种策略是树的重心。
我开始完全不理解,树的重心明明是各带权点距离重心距离和最小。
而题中要求的是最大的。这怎么会有关系
因为这个性质要求的是一群路,什么路呢
就是所有带权点(这道题是带权重心)和重心之间的连接和最小
而这种路一定能满足  每条边取min.
所以我们就可以先求 一个树的带权重心,然后就求每个带权点距离
重心的距离就行了。(这样的连接过程说明配对的过程都是要经过树的重心的)

或者说,根据贪心的思路,
我们不求树的带权重心了(为啥要求)
只需要贪心的求出每个边的 min,直接加起来就行了
不过求带权重心是为了说明 这种情况是能够成立的。

所以呢,我们直接用dfs,根据dfs序求出dfs方向的i点为根的siz,然后求min。
再遍历的时候,,每条边把min加起来就行。(并且弄一个反向的树)
这个比较方便,简单。
*/
const int maxn=4e5+20;
struct Node{
       int to;
       int next;
}node[maxn];
int len;
int head[maxn];
void add(int a,int b){
     node[len].to=b;
     node[len].next=head[a];
     head[a]=len++;
}
int m;
bool vis[maxn];
int c_tre[maxn];
int num[maxn];
bool visit[maxn];
int k;
long long all=0;
void Init(){
len=0;
memset(head,-1,sizeof(head));
memset(vis,false,sizeof(vis));
memset(c_tre,0,sizeof(c_tre));
memset(visit,false,sizeof(visit));
memset(num,0,sizeof(num));
all=0;
}
void dfs1(int x,int pre){
    if(visit[x]) {num[x]=1;}
    for(int i=head[x];i!=-1;i=node[i].next){
        int to=node[i].to;
        if(to==pre)continue;
        dfs1(to,x);
        num[x]+=num[to];
    }
    return ;//返回的这个num 带自身。
}
void  dfs(int x){
    vis[x]=true;
    for(int i=head[x];i!=-1;i=node[i].next)
   {    int to=node[i].to;
        if(vis[to]) continue;
        dfs(to);
        all+=1ll*min(2*k-num[to],num[to]);
   }
   return ;
}
int main()
{   int t;
    int a,b;
    scanf("%d%d",&m,&k);
          Init();
          for(int i=0;i<2*k;i++){
             scanf("%d",&a);
             visit[a]=true;
          }
          for(int i=0;i<m-1;i++){
              scanf("%d%d",&a,&b);
              add(b,a);
              add(a,b);
          }
          dfs1(1,-1);//记录一下权重
          dfs(1);//求和
          cout<<all<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值