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;
}