(树形DP) bzoj 1912
1912: [Apio2010]patrol 巡逻
Time Limit: 4 Sec Memory Limit: 64 MBSubmit: 684 Solved: 387
[Submit][Status][Discuss]
Description
Input
第一行包含两个整数 n, K(1 ≤ K ≤ 2)。接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n)。
Output
输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离。
Sample Input
8 1
1 2
3 1
3 4
5 3
7 5
8 5
5 6
1 2
3 1
3 4
5 3
7 5
8 5
5 6
Sample Output
11
HINT
10%的数据中,n ≤ 1000, K = 1;
30%的数据中,K = 1;
80%的数据中,每个村庄相邻的村庄数不超过 25;
90%的数据中,每个村庄相邻的村庄数不超过 150;
100%的数据中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。
Source
转载的。。很清晰啊
题目大意
给一棵树,求加k条边之后,从1号点遍历每个点之后再回到1号点的最小距离和。k=1或2
思路
如果不加边,那么遍历走过的距离显然是2(n−1)。注意到一个很有意思的限制:k=1或2。那么可以分类讨论以下两种情况
1、k=1,很显然最优的方法就是在树的最长路径x到y之间连一条边,这样x到y之间的距离缩短为1,这个最长路径就是树的直径,我们可以DFS求一遍树的直径len1,那么最终答案为2(n−1)−len1+1
另外为了情况2的方便,要在DFS过程中用链表记录下树的直径的路径。这个路径是两条路径相交而成的,假设这两条路径的交点为x:一条是x下面的最长路,另一条是x下面的次长路(注意它也是包含了y下面的最长路),如下图
除非次长路为0(x下面是一条链),否则直径一定是这样的形态
2、k=2,肯定是找树中次长路径x′到y′,给x′和y′之间连边,我们在情况1的基础上,将第一次DFS求得的树的直径上的所有边边权全部标为-1,再DFS一次,求出树中的次长路径len2,最终答案为2(n−1)−len1+1−len2+1
不知道自己的代码为嘛会WA了。。求指教啊。。。
#include<iostream> #include<cstdio> #include<cstring> #include<string> #include<cmath> #include<cstdlib> #include<algorithm> using namespace std; int n,k,cnt=1,maxx,st; struct node { int x,to,len,next; }e[200005]; int son1[200005],son2[200005],head[100005]; void add(int x,int y,int len) { cnt++; e[cnt].to=y; e[cnt].len=len; e[cnt].next=head[x]; head[x]=cnt; } int dfs(int u,int father) { int max1=0,max2=0; for(int i=head[u];i;i=e[i].next) { int v=e[i].to; int w=e[i].len; if(v==father) continue; int now=dfs(v,u)+w; if(now>max1) { max2=max1,son2[u]=son1[u]; max1=now,son1[u]=i; } else if(now>max2) { max2=now; son2[u]=i; } } if(maxx<(max1+max2)) maxx=max1+max2,st=u; return max1; } int main() { scanf("%d%d",&n,&k); for(int i=1;i<n;i++) { int x,y,z; scanf("%d%d",&x,&y); add(x,y,1); add(y,x,1); } int ans=0; ans=2*(n-1); dfs(1,-1); ans=ans-maxx+1; if(k==2) { for(int i=son1[st];i;i=son1[e[i].to]) e[i].len=e[i^1].len=-1; for(int i=son2[st];i;i=son2[e[i].to]) e[i].len=e[i^1].len=-1; dfs(1,-1); ans=ans-maxx+1; } printf("%d\n",ans); return 0; }