前置知识:求树的直径(dfs方法和树形dp方法)(D47 树的直径 B4016 树的直径 - 董晓 - 博客园)
题目意思:
n个点,n-1条边,从1号点出发,巡逻员要经过所有点之后再回到1号点,因为原图是无环,所以每条边都要经过两次,即2*(n-1),现在可以加k条边(1<=k<=2),加边只能被正好经过一次,问如何加边使得巡逻经过边数最少。
思路:如何加边使得巡逻经过边数最少--->加边就是连接两个点,形成一个环,两点之间的边都可以节省一次经过,则要找两个点,使得两个点之间的边最多
1.(K=1)加一条:连接直径(最长路径),直径和新边构成环,节省的长度就是直径d1(直径上走了一遍),那路径长度就为2*(n-1)-d1+1(加边);
2.(K=2)再加一条:找到次长路径d2,
①在d1,d2没有重合边的情况下,路径长度为2 *(n-1)-d1+1-d2+1;
②在d1,d2有重合边的情况下,重合边走了两遍,没有节省,设置重合边为x,节省的路径要在原来d1+d2的基础上减去2*x; 求d1时还不知道重合边为哪些,边权都是1,用两遍dfs求(dfs可以记录路径,把d1路径染色(for(int i=p;i;i=fa[p)col[i]=1),求d2时,正常边权为1,如果col[u]==col[v]==1,则u-v边为重合边,在d2中边权为0,其实这些边在d1z中应该也算成0,但是在求d1时,视为1,所以还要再减去一遍,则把这些边的权值设为-1,求出d2,路径长度为2 *(n-1)-d1+1-d2+1。
完整代码:
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
const int N=1e5+10;
int n,k,d1,d2,p;
vector<int>e[N];
int col[N],fa[N],d[N];
void dfs(int u,int ff)
{
fa[u]=ff;
for(int v:e[u])
{
if(v!=ff)
{
d[v]=d[u]+1;
if(d[v]>d[p]) p=v;
dfs(v,u);
}
}
}
int dp(int u,int ff)
{
int dd1=0,dd2=0,dd;
for(int v:e[u])
{
int w=1;
if(v!=ff)
{
if(col[v]&&col[u]) w=-1;
dd=dp(v,u)+w;
if(dd>dd1) dd2=dd1,dd1=dd;
else if(dd>dd2) dd2=dd;
}
}
d2=max(d2,dd1+dd2);
return dd1;
}
int main()
{
cin>>n>>k;
for(int i=1;i<n;i++)
{
int x,y;
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
d[p]=0;
dfs(p,0);
d1=d[p];
if(k==1)
{
cout<<2*(n-1)-d1+1<<endl;
return 0;
}
for(int i=p;i;i=fa[i]) col[i]=1;
memset(d,0,sizeof d);
dp(1,0);
cout<<2*(n-1)-d1+1-d2+1<<endl;
return 0;
}
&spm=1001.2101.3001.5002&articleId=147879702&d=1&t=3&u=02233688c73742b1b17f98c60259560f)

被折叠的 条评论
为什么被折叠?



