[luogu]P3629 [APIO2010]巡逻

原题链接:P3629 [APIO2010]巡逻

题意

给定一棵树,每次要走过每条边。

现在要求加$k$条边,使得走过每条边后走过的距离最小,求这个最小值。

分析

首先不考虑加边,也就是说$k=0$时,是一棵树,显然每条边要经过两次。

考虑加一条边,很容易发现形成的环上的点只需要经过一遍,最终经过的距离就是$((n-1) imes 2-len+2)$,$len$是最大环的长度,明显是树的直径$+1$。

考虑加两条边对答案有什么影响。

分两类考虑:

1.第一次形成的环和第二次没重叠:答案继续减去$len_2+2$.

2.第一次形成的环和第二次有重叠:画过图就很容易发现,重叠部分是经过了两次的,答案就是$((n-1) imes 2-len_1+2_len_2+2+k)$,$k$是这个重叠部分的长度。

那么我们就考虑怎么求出这个重叠部分了。

考虑差分。

第一次已经减去了这个长度,第二次减去这个长度的相反数就相当于加回上了这个长度。

那么我们只需要把直径上的边权置为$-1$,再求一次树的直径即可。

注意第二次的直径可以为$0$(自环)。

实现的方式

我们常用dfs写树的直径,但是我们发现这样子是不能跑负权图的,因为dfs不能统计负权的情况。

而树形dp求树的直径虽然可以跑负权图,但是却很难转移出其他量。

我们结合这两种算法,在第一次时用dfs求出直径,并保存出直径上的边,第二次再跑一次树形dp就可以了。

保存直径上的边的时候,我们需要一点小技巧。

运用费用流中求增广路的方法,我们保存每个节点的祖先节点(从哪里转移过来),并利用成对储存的方法保存每条边和它的反向边。

具体实现见代码。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+1000;
int read(){
    char c;int num,f=1;
    while(c=getchar(),!isdigit(c))if(c=='-')f=-1;num=c-'0';
    while(c=getchar(), isdigit(c))num=num*10+c-'0';
    return f*num;
}
int n,k,ans,pre[N*2],maxn,id,dis[N*2];
int head[N],nxt[N*2],ver[N*2],edge[N*2],tot=1;
void add_edge(int u,int v,int w){
    ver[++tot]=v;edge[tot]=w;nxt[tot]=head[u];head[u]=tot;
    ver[++tot]=u;edge[tot]=w;nxt[tot]=head[v];head[v]=tot;
}
void dfs(int x,int val,int pr){
    if(val>maxn){
        maxn=val;
        id=x;
    }
    for(int i=head[x];i;i=nxt[i]){
        if(ver[i]==pr)continue;
        pre[ver[i]]=i;
        dfs(ver[i],val+edge[i],x);
    }
}
int dfs_get_diam(){
    dfs(1,0,1);
    memset(pre,0,sizeof(pre));
    maxn=0;
    dfs(id,0,id);
    return maxn;
}
void dp_get_diam(int x,int fa){
    for(int i=head[x];i;i=nxt[i]){
        if(ver[i]==fa)continue;
        dp_get_diam(ver[i],x);
        maxn=max(maxn,dis[x]+dis[ver[i]]+edge[i]);
        dis[x]=max(dis[x],dis[ver[i]]+edge[i]);
    }
}
int main()
{
    n=read();k=read();
    for(int i=1;i<n;i++){
        int u,v;
        u=read();v=read();
        add_edge(u,v,1);
        ans+=2;
    }
    ans-=dfs_get_diam()-1;
    if(k==1){
        printf("%d
",ans);
        return 0;
    }
    for(int i=pre[id];i;i=pre[ver[i^1]]){
        edge[i]=-1;
        edge[i^1]=-1;
    }
    maxn=0;
    dp_get_diam(1,1);
    ans-=maxn-1;
    printf("%d
",ans);
    return 0;
}
View Code
原文地址:https://www.cnblogs.com/onglublog/p/10030017.html