=== ===
这里放传送门
=== ===
题解
因为一开始的图是一棵树,所以在不加边的情况下每条边一定会被经过两次,巡逻的代价固定为
ans=2∗(n−1)
。如果在树上加一条边,那么就会有一条链不需要走两次,这条链的起点和终点就是新边的起点和终点。那么如果只能在树上加一条边的话,和这条边构成环的链一定是越长越好。于是找树上最长链就能解决问题。
第一次求最长链的时候可以用dfs也可以DP,这里用了dfs,方法是先从任意一个点dfs找到最远的那个点,再从这个点dfs第二遍,找到另一个最远点,这两个点中间的那条链就是最长的,代价就变为
ans−len+1
,
len
为链的长度。
可是当k=2的时候怎么办呢?题目中要求每条边必须经过一次,也就是说为了满足这个要求,上一次加边后本来可以只被经过一次的某些边可能被”强行“经过两次,也就是说这条链形成的环如果与第一次找到的链有重叠,那么这条重叠的边仍然需要走两遍,相当于没有加也没有减。那么就把上一次选定的链上的每条边边权都赋值为-1,再跑一边最长链就可以了。而这就出现了一个问题就是dfs不适用了,因为出现了负边权。于是这时使用了树形dp,用
f
数组和
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,k,tot,p[100010],a[200010],w[200010],next[200010],lc,ld,pre[100010],ans,st;
int wer,f[100010],g[100010];
void add(int x,int y,int v){
a[tot]=y;w[tot]=v;next[tot]=p[x];p[x]=tot++;
}
void dfs(int u,int last,int len){
for (int i=p[u];i!=-1;i=next[i])
if (a[i]!=last){
if (len+w[i]>ld){
ld=len+w[i];lc=a[i];
}//找到最长链
pre[a[i]]=i;
dfs(a[i],u,len+w[i]);
}
}
void dp(int u,int last){
for (int i=p[u];i!=-1;i=next[i])
if (a[i]!=last){
int v1,v2;
dp(a[i],u);
v1=f[a[i]]+w[i];
if (v1>f[u]){//判断这个子树的答案应该更新f还是更新g,只能更新一个
g[u]=f[u];f[u]=v1;
}else g[u]=max(g[u],v1);
}
wer=max(wer,f[u]+g[u]);
}
int main()
{
memset(p,-1,sizeof(p));
scanf("%d%d",&n,&k);
for (int i=1;i<n;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v,1);add(v,u,1);
}
ans=2*(n-1);
dfs(1,0,0);
memset(pre,0,sizeof(pre));
ld=0;st=lc;dfs(st,0,0);//两次dfs,第一次的pre数组没有什么意义
ans=ans-ld+1;
if (k==2){
for (int i=lc;i!=st;i=a[pre[i]^1])
w[pre[i]]=w[pre[i]^1]=-1;//修改最长链上的边权
wer=-0x7fffffff;dp(1,0);
ans=ans-wer+1;
}
printf("%d\n",ans);
return 0;
}
偏偏在最后出现的补充说明
DP的时候注意最长链和次长链不能在同一棵子树上。所以一棵子树的答案对于父节点来说要么更新
f
,要么更新