zoukankan      html  css  js  c++  java
  • [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
  • 相关阅读:
    超全的IE兼容性问题及解决方案
    排序算法之冒泡排序
    Java集合框架之图解(更新中...)
    ProgressBar与Handler的整合应用
    构建一个自己的springBoot启动器
    浅谈JVM(上)
    什么是JUC以及基本线程的知识
    Zookeeper 注册中心安装
    Spring Cloud 之 链路追踪Sleuth和Zipkin,RabbitMQ整合(十七)
    Spring Cloud 之 链路追踪Sleuth和Zipkin整合(十六)
  • 原文地址:https://www.cnblogs.com/onglublog/p/10030017.html
Copyright © 2011-2022 走看看