题目就是给出一颗树,对于每一个点,如果改变树的一条边(可以不变),是否能让这个点变成重心;
zzh学长教我时,告诉我这是贪心(其实好像是树形dp),因为重心的最大的子树大小不超过全树的一半,所以贪心也是方便的,即尽可能让某个点的最大的子树大小不超过全树的一半;所谓改变一条边,显然是在某点的最大的子树里砍掉一边,添加到别的地方,这样可以使整棵树相对于这个点更平衡;
但是我们要删掉哪一条边,添加到哪里,这就要贪心了;
设询问某点x
我们知道,原先的树也是有重心的,设其是y;显然,y一定在x的最大的子树里;如果我们要建立一个新的重心x,就一定要破坏原先的重心y,很显然,改变的最优方法必然是在原重心y附近截取一个子树移到x上;
显然的,割掉原先y的最大子树接到x上是比较好的,因为y的最大子树一定不超过全树的一半,且去掉y的最大子接到x上,重心一定会从y向x移;感觉就是这样,把y的最大子树移动到x上,最优;
但本蒟蒻无法证明~~~;
当然咯,如果x恰好就在y的最大子树上,那就尴尬了,要判断两种较优情况
1.取y的次大子树接到x上;
2.把割掉y的最大子树后的那颗树接到x上;
就是这样,感觉这个贪心蒙出来的,还是dp靠谱
如果原来有好多重心,任意
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
struct cs{
int to,next;
}a[800009];
int head[400010],v[400005],vv[400005];//链表存,v[i]是i节点的最大子树,vv[i]是次大
bool b[400005];//标记点i是否在原重心的最大子树上
int n,m,x,y,ans,ll,maxc,z;
void inc(int x,int y){
ll++;
a[ll].to=y;
a[ll].next=head[x];
head[x]=ll;
}
int dfs(int x,int y){
int k=head[x],an1=0,an=0,c=0,ann=0;
int sum=1;
while(k){
if(a[k].to!=y)an=dfs(a[k].to,x);else an=0;
sum+=an;
if(an>an1){
if(an1>ann)ann=an1;
an1=an;c=a[k].to;
}else if(an>ann)ann=an;
k=a[k].next;
}
an=n-sum;
if(an>an1){
if(an1>ann)ann=an1;
an1=an;c=y;
}else if(an>ann)ann=an;
v[x]=an1;vv[x]=ann;
if(an1<=n/2){
ans=x;maxc=c;
}
return sum;
}
int dfs2(int x,int y){
int k=head[x];
while(k){
if(a[k].to!=y){
b[a[k].to]=1;
dfs2(a[k].to,x);
}
k=a[k].next;
}
}
int main()
{
memset(head,0,sizeof head);
ll=0;
scanf("%d",&n);
for(int i=1;i<=n-1;i++){
scanf("%d%d",&x,&y);
inc(x,y);
inc(y,x);
}
int k=head[1],an1=0,an=0,c=0,ann=0;
while(k){
an=dfs(a[k].to,1);
if(an>an1){
if(an1>ann)ann=an1;
an1=an;c=a[k].to;
}else if(an>ann)ann=an;
k=a[k].next;
}
v[1]=an1; vv[1]=ann;
if(an1<=n/2){
ans=1;maxc=c;
}
b[maxc]=1;
dfs2(maxc,ans);
for(int i=1;i<=n;i++){
if(b[i]){
if(max(v[i]-vv[ans],vv[ans])<=n/2){printf("1 ");continue;}
if(n-v[ans]<=n/2){printf("1 ");continue;}
printf("0 ");
}else{
if(max(v[ans],v[i]-v[ans])<=n/2){printf("1 ");continue;}
printf("0 ");
}
}
}
另外树形dp是思想和贪心差不多;
就是找到以x为根节点的时候,所有子树里面节点数最大又不超过n/2的子树,把他提出来;