巡逻
给定一棵树 边权为1 需要从一号节点出发走每一条边再回到一号节点
现在允许添加一条或者两条边 添加的边只能走一次 求最短路径
首先考虑k=1的情况 显然连接直径两端即可
在考虑k=2的情况 我们发现
\[ans=2(n-1)-d1+1-d2+1+\cup(d_1,d_2)
\]
合并
\[ans=ans=2n-d1-(d2-\cup(d_1,d_2))
\]
因此把第一次求得直径去相反数即可
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int read()
{
int x=0,f=0,c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return f?-x:x;
}
struct Edge
{
int to,next;
}e[N<<1];
int head[N],cnt;
void _add(int a,int b){ e[++cnt]=(Edge){b,head[a]}; head[a]=cnt;}
void add(int a,int b){ _add(a,b); _add(b,a); }
bool vis[N],tag[N];
int dis[N],fa[N];
int n,k;
int dfs(int x)
{
vis[x]=1; int ret=0,tmp=0;
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to;
if(vis[y]) continue;
dis[y]=dis[x]+1; fa[y]=x;
tmp=dfs(y);
if( dis[tmp]>dis[ret]||!ret) ret=tmp;//这里一定要注意处理首次的情况 tmp和ret是不同的值
}
if(!tmp) return x;
return ret;
}
int ans=0;
void dp(int x)
{
vis[x]=1;
for(int i=head[x];i;i=e[i].next)
{
int y=e[i].to;
if(vis[y]) continue;
dp(y);
int tmp= (tag[x]&&tag[y])?dis[y]-1:dis[y]+1;
ans=max(ans,dis[x]+tmp);
dis[x]=max(dis[x],tmp);
}
}
int find(int t)
{
memset(vis,0,sizeof vis);
memset(dis,0,sizeof dis);
return dfs(t);
}
int main()
{
n=read(); k=read();
for(int i=1;i<n;i++) add(read(),read());
int S=find(1); int T=find(S);
if(k==1){
printf("%d",2*(n-1)-dis[T]+1); return 0;}
int tmp=dis[T];
while(T!=S){ tag[T]=1; T=fa[T];}tag[S]=1;
memset(vis,0,sizeof vis);
memset(dis,0,sizeof dis);
dp(1);
printf("%d",2*n-tmp-ans);
return 0;
}
注意:
通过数学上的合并 更改图的性质 再套用模板
并集去相反数也是很常用的思路
两遍dfs只能用于边权非负
树形dp求直径的操作思路是:
统计子树内的最长链(不跨过根),但是在统计答案的时候合并

浙公网安备 33010602011771号