题目地址:https://www.acwing.com/problem/content/352/
题意:在以可以1为根的树中增加k条边,巡警从1出发单位时间内可以走过一条边问巡警走过所有边最后回到1号点的最小耗时是多少?
分析:对于一棵大小为n的树来说,有n - 1条边每一条边都要走一遍然后再回到1号点以为着每一条边都要走两遍耗时为2 * (n - 1)
若在i和j之间连一条边从i(树中就会出现一个环)出发走到j经过的所有边只需要走一次,然后沿着新加的边就可以从j走回到i,减少的耗时就为(i到j之间的边数)- 1
如果只加一条边那么只要使(i到j之间的边数)- 1的值最大就和以得到最小耗时而-1是定值所以要使(i到j之间的边数)最大,即求树的直径
如果要加两条边,第一条边按照上面的方法构造,那么第二条边是否可以按照第一条边的方式构造呢?
假设第二条边在i和j之间相连可以使答案最优那么答案是多少?
如果添加了第二条边后形成的环和第一条边形成的环没有公共边那么就是2 * (n - 1) - 这两条边相连的两个点之间的距离之和 - 2
如果第二条边和第一条边有重合,这两个环必须按照8字形的走法才能将环上的所有边走一遍,所以重合的部分又必须走两遍了
那么答案就等于2 * (n - 1) - 这两条边相连的两个点之间的距离之和 + 两个环重合的部分 * 2 - 2
整理一下2 * (n - 1) - 第一条边相连的两个点之间的距离之和 - 第二条边相连的两个点之间的距离之和 + 两个环重合的部分 * 2 - 2
= 2 * (n - 1) - 第一条边相连的两个点之间的距离 - (第二条边相连的两个点之间的距离除去重合部分 + 重合部分 - 两个环重合的部分 * 2 ) - 2
= 2 * (n - 1) - 第一条边相连的两个点之间的距离 - (第二条边相连的两个点之间的距离除去重合部分 - 重合部分) - 2
第一条边相连的两个点之间的距离 可以直接求树的直径
(第二条边相连的两个点之间的距离除去重合部分 - 重合部分) 将重合部分的权值置为-1其他部分边的权值为,在这样的树中求一个权值之和最大的路径(可以用求直径的方法求)
代码:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N = 100010; int h[N],ne[N * 2],e[N * 2],w[N * 2],idx; int p[N][2];//从i往哪个儿子向下走能走到最长路径和次长路径 int ans,cen;//答案,直径所在路径上的某一个点 int n,k; void add(int a,int b) { e[idx] = b,w[idx] = 1,ne[idx] = h[a],h[a] = idx++; } int dfs(int u,int father) { int d1 = 0,d2 = 0; p[u][0] = p[u][1] = -1; for(int i = h[u];~i;i = ne[i]) { int j = e[i]; if(j == father) continue; int t = dfs(j,u) + w[i]; if(t >= d1) { d2 = d1,d1 = t; p[u][1] = p[u][0],p[u][0] = i;//先更新磁场的在更新最长的 }else if(t > d2) d2 = t,p[u][1] = i; } if(ans < d1 + d2) { ans = d1 + d2; cen = u; } return d1; } int main() { scanf("%d%d",&n,&k); memset(h,-1,sizeof h); for(int i = 1;i < n;i ++) { int a,b; scanf("%d%d",&a,&b); add(a,b); add(b,a); } dfs(1,0); int L1 = ans - 1,L2 = 0; if(k > 1) { for(int i = p[cen][0];~i;i = p[e[i]][0]) w[i] = -1; for(int i = p[cen][1];~i;i = p[e[i]][0]) w[i] = -1;//是e[i] ans = 0; dfs(1,0); L2 = ans - 1; } printf("%d\n",2 * (n - 1) - L1 - L2); return 0; }