传送门1
传送门2
思路:
我也不知道该说是什么算法……
显然k=1时答案就是求树的直径d
然后答案就是
2n−d+1
然后k=2当时的我并不会怎么做……
觉得是在k=1的基础上再在各个外向树上再找一发最大直径
然后就感人肺腑地过了21个点(总共30个点)……
容易发现这样贪心是错误的
因为答案可能是两棵外向树相互连接
那怎么办?
我们画个非常抽象的图来感受一下(高能)
上面就是连完两条边后图中存在的两个环的情况(没有画出环上的树)
红色的边就是两个环重复的边
我们发现红色的边是必然要走两次的,而环上的其他边只用走一次
也就是说这些红色的边和原来树上的边在本质上没有区别,都是要被计算进答案两次的
我们这样来考虑
k=0时答案是
2(n−1)
,这是显然的
当加入一条边时,答案本会变成
2n
,但树上会形成一个环,这个环上的每条边从走两次变成了走一次,对答案的贡献是-1,设这个环共有x条边(包括加入的那条边),答案就是
2n−x
,由于
x=d−1
,所以k=1时答案就是
2n−d+1
当在加入一条边的基础上再加入一条边,又会形成一个新的环,这两个环共有的每条边从走一次变成了走两次,对答案的贡献就成了1,设第一个环有a条边,第二个环有b条边,它们共有的边是c条,那么答案就是
2n+2−(a−c)−(b−c)
实际上也就是说,在加入第二条边时,每条非环边的贡献是-1,每条环上边的贡献是1,我们要让贡献最小
具体做法就是令树的边权都为1,先求一下树的直径
d1
,把直径上的边权变为-1,然后再求一遍直径
d2
那么答案就是
2n−d1−d2+2
其中
d1=a,d2=b−2c
由于存在负边权,我一开始是想用奇怪的方法来改进bfs,使其能求负权树的边权,调试多次未果后只能改成dfs,用类似树形dp的方法来求,具体做法就是dfs整棵树,对每一棵子树记录一个最大值
mx
和次大值
sx
,
d2=max(d2,mx+sx)
,返回值为
mx
复杂度
O(n)
代码:
#include<cstdio>
#include<iostream>
#include<queue>
#include<cstring>
#define M 100005
using namespace std;
int n,k,tot=1,cnt,ans;
int first[M],dis[M],up[M];
struct edge{
int v,w,next;
}e[M<<1];
void add(int x,int y)
{
e[++tot].v=y;e[tot].w=1;e[tot].next=first[x];first[x]=tot;
e[++tot].v=x;e[tot].w=1;e[tot].next=first[y];first[y]=tot;
}
queue<int>q;
int bfs(int x)
{
int t=0;
memset(dis,0,sizeof(dis));
for (dis[x]=0,q.push(x);!q.empty();q.pop())
{
x=q.front();
for (int i=first[x];i;i=e[i].next)
if (!dis[e[i].v])
dis[e[i].v]=dis[x]+e[i].w,
t=(!t||dis[t]<dis[e[i].v]?e[i].v:t),
up[e[i].v]=i,
q.push(e[i].v);
}
return t;
}
int dfs(int x,int fa)
{
int now,sx=0,mx=0;
for (int i=first[x];i;i=e[i].next)
if (e[i].v!=fa)
{
now=dfs(e[i].v,x)+e[i].w;
if (now>mx)
sx=mx,mx=now;
else if (sx<now&&now<=mx)
sx=now;
cnt=max(cnt,mx+sx);
}
return mx;
}
main()
{
scanf("%d%d",&n,&k);
int x,y,mx;
for (int i=1;i<n;++i)
scanf("%d%d",&x,&y),
add(x,y);
x=bfs(1),y=bfs(x);
ans=n-1<<1;
ans-=dis[y]-1;
for (;y!=x;y=e[up[y]^1].v)
e[up[y]].w=e[up[y]^1].w=-1;
if (k==1) return printf("%d\n",ans),0;
dfs(1,0);
printf("%d\n",ans-cnt+1);
}