题目描述
在一个地区有 n 个村庄,编号为1,2,…,n。
有 n-1 条道路连接着这些村庄,每条道路刚好连接两个村庄,从任何一个村庄,都可以通过这些道路到达其他任一个村庄。
每条道路的长度均为1个单位。
为保证该地区的安全,巡警车每天都要到所有的道路上巡逻。
警察局设在编号为1的村庄里,每天巡警车总是从警局出发,最终又回到警局。
为了减少总的巡逻距离,该地区准备在这些村庄之间建立 K 条新的道路,每条新道路可以连接任意两个村庄。
两条新道路可以在同一个村庄会合或结束,甚至新道路可以是一个环。
因为资金有限,所以 K 只能为1或2。
同时,为了不浪费资金,每天巡警车必须经过新建的道路正好一次。
编写一个程序,在给定村庄间道路信息和需要新建的道路数的情况下,计算出最佳的新建道路的方案,使得总的巡逻距离最小。
输入格式
第一行包含两个整数 n 和 K。
接下来 n-1 行每行两个整数 a 和 b,表示村庄 a 和 b 之间有一条道路。
输出格式
输出一个整数,表示新建了 K 条道路后能达到的最小巡逻距离。
数据范围
3≤n≤100000,
1≤K≤2,
1≤a,b≤n
样例
输入样例:
8 1
1 2
3 1
3 4
5 3
7 5
8 5
5 6
输出样例:
11
算法
题目大意:一棵树,添加k条边使得遍历所有的点的总代价最小,其中1<=k<=2;
首先呢:不建道路,路线总长度2*(n-1);
我们分析当k等于1,此时的最优解无疑是将边加到直径两端;那么答案就是2*(n-1)-d+1;这样你会得到30分;
其实我们考虑k=2时,对于一棵树,我们加边会形成一个环,加两条边形成两个环,我们就要考虑这两个环的关系了;
1.两个环上的边不重叠;
2.两个环上的边有重叠部分;
对于第一种情况,我们只需要再次找一下最长链,忽略直径哈,答案减小;
那么对于第二种,如果我们将其作为加1条边的请况,那么两个环重叠的不会将不会被经过,和题目要求不符,重叠部分则需要经过两次,所以我们需要让巡逻车在适当时候重新巡逻这些边,并且返回,最终的结果是我们让两个环上的重叠的边经过了两次;
所以:
1、求树的直径L1,将L1上的边权取反,变为-1;
2、再求树的直径L2,答案即为2(n−1)−L1+1−L2+1=2∗n−L1−L2;
如果L2这条直径包含L1取反的部分,就相当于重叠,减掉(L1-1),重叠的边经过了一次,减掉(L2-1),把重叠部分加回来,变为需要经过两次;
时间复杂度O(N);
对于找树的直径,有bfs(dfs)和dp两种做法:
但是bfs的做法遇到负权好像不太行,但是dp可以,但是dp好像只可以求个直径;
C++ 代码
#include<bits/stdc++.h>
using namespace std;
#define N 500010
template<typename T>inline void read(T &x)
{
x=0;T f=1,ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
x*=f;
}
struct gg {
int y,next,v;
}a[N<<1];
int lin[N],vis[N],dis[N],dis1[N],father[N],tot=1,n,k,ans,d,x1,x2;
inline void add(int x,int y,int z) {
a[++tot].next=lin[x];
a[tot].y=y;
a[tot].v=z;
lin[x]=tot;
}
queue<int> q;
inline int bfs(int s) {
memset(vis,0,sizeof(vis));
int point=s;
q.push(s);
dis[s]=0;vis[s]=1;
father[s]=0;
while(!q.empty()) {
int x=q.front();q.pop();
for(int i=lin[x];i;i=a[i].next) {
int y=a[i].y;
if(!vis[y]) {
vis[y]=1;q.push(y);
dis[y]=dis[x]+a[i].v;
father[y]=x;
if(dis[point]<dis[y]) point=y;
}
}
}
return point;
}
inline void dp(int u) {
int maxn=0;
for (int i=lin[u];i;i=a[i].next) {
int v=a[i].y;
if (v!=father[u]) {
dp(v);
ans=max(ans,maxn+dis1[v]+a[i].v);
maxn=max(maxn,dis1[v]+a[i].v);
}
}
ans=max(ans,maxn);
dis1[u]=maxn;
}
inline void get() {
x1=bfs(1);
x2=bfs(x1);
bfs(1);
memset(vis,0,sizeof(vis));
if(dis[x1]<dis[x2]) swap(x1,x2);
vis[x1]=vis[x2]=1;
while(dis[x1]>dis[x2]) {
x1=father[x1];
vis[x1]=1;
++d;
}
while (x1!=x2) {
x1=father[x1];
x2=father[x2];
vis[x1]=vis[x2]=1;
d+=2;
}
}
inline void tab(int x) {
for(int i=lin[x];i;i=a[i].next) {
int y=a[i].y;
if(y!=father[x]) {
if(vis[x]&&vis[y]) {
a[i].v=a[i^1].v=-1;
}
tab(y);
}
}
}
int main() {
//freopen("data.in","r",stdin);
//freopen("data.ans","w",stdout);
read(n);read(k);
int x,y;
for(int i=1;i<n;i++) {
read(x);read(y);
add(x,y,1);add(y,x,1);
}
get();
if(k==1) {
printf("%d\n",2*(n-1)-d+1);
exit(0);
}
if(d==n-1) {
printf("%d\n",n+1);
exit(0);
}
else {
tab(1);
dp(1);
printf("%d\n",2*n-ans-d);
}
return 0;
}