题意:一棵n节点的树,每个节点有一个人,每个人离开自己的位置到另一个位置,每个位置只能有一个人,问这n个人移动距离和的最大值。
思路:11年成都区赛的题目,刚开始想着是匹配问题,但是求出任意两点的距离是不行的,后来想到如果找出树的重心就可以把树分成左右两部分,左边的点跟右边的点交换位置,两点交换位置的距离=两点到根节点的距离之和的2倍,总距离就是所有点到根节点距离之和的2倍了,当时犹豫了一下,如果节点数是奇数的话,这样是不是根节点没换位置呢?后来一想,这种交换位置每个点走的路径都经过根节点,根节点跟任意一个点交换一下就可以了,答案还是一样的。由于没考虑超int的问题wrong了,后来又想到用树形DP可以做,就改了算法。
树形DP:我们可以统计没条边被走了多少次,一条边可以把点分为左右两部分,两部分中点数较少的一部分都要离开自己的位置去另一边,这条边被走了min(son[u](右边点数),son[v])*2次,点数多的一部分有的位置是没变的,但是我们这样把每条边都操作一遍后,所有点的位置都变了。
两种方法的原理都是一样的,都是将树重心左边的点跟右边的点交换,树形DP不用求树的重心。
树形DP:
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<stdio.h> #include<string.h> const int N=100100; int head[N],num,dis[N],son[N],f[N],size,root; __int64 ans; struct edge { int ed,w,next; }e[N*2]; void addedge(int x,int y,int w) { e[num].ed=y;e[num].w=w;e[num].next=head[x];head[x]=num++; e[num].ed=x;e[num].w=w;e[num].next=head[y];head[y]=num++; } int min(int a,int b) { if(a<b)return a; return b; } void dfs(int u,int fa) { int i,v; son[u]=1; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].ed; if(v==fa)continue; dfs(v,u); son[u]+=son[v]; __int64 minson=min(son[v],size-son[v]);//边将点分为两部分,求最少的一部分 ans+=e[i].w*minson*2; } } int main() { int i,n,t,op=1,x,y,w,sum; scanf("%d",&t); while(t--) { memset(head,-1,sizeof(head)); sum=0;num=0; scanf("%d",&n); for(i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&w); addedge(x,y,w); } ans=0;size=n; dfs(1,0); printf("Case #%d: %I64d\n",op++,ans); } return 0; }
找树的重心然后求点到根节点的距离:
#pragma comment(linker, "/STACK:1024000000,1024000000") #include<stdio.h> #include<string.h> const int N=100100; int head[N],num,son[N],f[N],size,root; __int64 dis[N],ans; struct edge { int ed,w,next; }e[N*2]; void addedge(int x,int y,int w) { e[num].ed=y;e[num].w=w;e[num].next=head[x];head[x]=num++; e[num].ed=x;e[num].w=w;e[num].next=head[y];head[y]=num++; } int max(int a,int b) { if(a>b)return a; return b; } void getroot(int u,int father)//求树的重心 { int i,v; f[u]=0;son[u]=1; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].ed; if(v==father)continue; getroot(v,u); son[u]+=son[v]; f[u]=max(f[u],son[v]); } f[u]=max(f[u],size-son[u]); if(f[u]<f[root])root=u; } void dfs(int u,int fa) { int i,v; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].ed; if(v==fa)continue; dis[v]=dis[u]+e[i].w; dfs(v,u); } ans+=dis[u]; } int main() { int i,n,t,op=1,x,y,w; scanf("%d",&t); while(t--) { memset(head,-1,sizeof(head)); ans=0;num=0; scanf("%d",&n); for(i=1;i<n;i++) { scanf("%d%d%d",&x,&y,&w); addedge(x,y,w); } root=0;f[root]=size=n; getroot(1,0); dis[root]=0; dfs(root,0);//树的重心为根节点 printf("Case #%d: %I64d\n",op++,ans*2); } return 0; }