[APIO2010]巡逻 题解

题目:传送门

题目大意:一棵树,添加k条边使得遍历所有的点的总代价最小,其中1<=k<=2;

首先呢:不建道路,路线总长度2*(n-1);

我们分析当k等于1,此时的最优解无疑是将边加到直径两端;那么答案就是2*(n-1)-d+1;这样你会得到30分;

其实我们考虑k=2时,对于一棵树,我们加边会形成一个环,加两条边形成两个环,我们就要考虑这两个环的关系了;

1.两个环上的边不重叠;

2.两个环上的边有重叠部分;

对于第一种情况,我们只需要再次找一下最长链,忽略直径哈,答案减小;

那么对于第二种,如果我们将其作为加1条边的请况,那么两个环重叠的不会将不会被经过,和题目要求不符,重叠部分则需要经过两次,所以我们需要让巡逻车在适当时候重新巡逻这些边,并且返回,最终的结果是我们让两个环上的重叠的边经过了两次;

所以:

1、求树的直径L1,将L1上的边权取反,变为-1;
2、再求树的直径L2,答案即为2(n−1)−L1+1−L2+1=2∗n−L1−L2;

如果L2这条直径包含L1取反的部分,就相当于重叠,减掉(L1-1),重叠的边经过了一次,减掉(L2-1),把重叠部分加回来,变为需要经过两次;

时间复杂度O(N);

对于找树的直径,有bfs(dfs)和dp两种做法:

但是bfs的做法遇到负权好像不太行,但是dp可以,但是dp好像只可以求个直径;

#include<bits/stdc++.h>
using namespace std;
#define N 500010
template<typename T>inline void read(T &x)
{
    x=0;T f=1,ch=getchar();
    while(!isdigit(ch)) {if(ch=='-') f=-1;ch=getchar();}
    while(isdigit(ch))  {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x*=f;
}
struct gg {
    int y,next,v;
}a[N<<1];
int lin[N],vis[N],dis[N],dis1[N],father[N],tot=1,n,k,ans,d,x1,x2;
inline void add(int x,int y,int z) {
    a[++tot].next=lin[x];
    a[tot].y=y;
    a[tot].v=z;
    lin[x]=tot;
}
queue<int> q;
inline int bfs(int s) {
    memset(vis,0,sizeof(vis));
    int point=s;
    q.push(s);
    dis[s]=0;vis[s]=1;
    father[s]=0;
    while(!q.empty()) {
        int x=q.front();q.pop();
        for(int i=lin[x];i;i=a[i].next) {
            int y=a[i].y;
            if(!vis[y]) {
                vis[y]=1;q.push(y);
                dis[y]=dis[x]+a[i].v;
                father[y]=x;
                if(dis[point]<dis[y]) point=y;
            }
        }
    }
    return point;
}
inline void dp(int u) {
    int maxn=0;
    for (int i=lin[u];i;i=a[i].next) {
        int v=a[i].y;
        if (v!=father[u]) {
            dp(v);
            ans=max(ans,maxn+dis1[v]+a[i].v);
            maxn=max(maxn,dis1[v]+a[i].v);
        }
    }
    ans=max(ans,maxn);
    dis1[u]=maxn;
}
inline void get() {
    x1=bfs(1);
    x2=bfs(x1);
    bfs(1);
    memset(vis,0,sizeof(vis));
    if(dis[x1]<dis[x2]) swap(x1,x2);
    vis[x1]=vis[x2]=1;
    while(dis[x1]>dis[x2]) {
        x1=father[x1];
        vis[x1]=1;
        ++d;
    }
    while (x1!=x2) {
        x1=father[x1];
        x2=father[x2];
        vis[x1]=vis[x2]=1;
        d+=2;
    }
}
inline void tab(int x) {
    for(int i=lin[x];i;i=a[i].next) {
        int y=a[i].y;
        if(y!=father[x]) {
            if(vis[x]&&vis[y]) {
                a[i].v=a[i^1].v=-1;
            }
            tab(y);
        }
    }
}
int main() {
    //freopen("data.in","r",stdin);
    //freopen("data.ans","w",stdout);
    read(n);read(k);
    int x,y;
    for(int i=1;i<n;i++) {
        read(x);read(y);
        add(x,y,1);add(y,x,1);
    }
    get();
    if(k==1) {
        printf("%d\n",2*(n-1)-d+1);
        exit(0); 
    }
    if(d==n-1) {
        printf("%d\n",n+1);
        exit(0);
    }
    else {
        tab(1);
        dp(1);
        printf("%d\n",2*n-ans-d);
    }    
    return 0;
}
View Code

 

 

转载于:https://www.cnblogs.com/Tyouchie/p/10808247.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
校园悬赏任务平台对字典管理、论坛管理、任务资讯任务资讯公告管理、接取用户管理、任务管理、任务咨询管理、任务收藏管理、任务评价管理、任务订单管理、发布用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行校园悬赏任务平台程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。校园悬赏任务平台的开发让用户查看任务信息变得容易,让管理员高效管理任务信息。 校园悬赏任务平台具有管理员角色,用户角色,这几个操作权限。 校园悬赏任务平台针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理任务信息,管理任务资讯公告信息等内容。 校园悬赏任务平台针对用户设置的功能有:查看并修改个人信息,查看任务信息,查看任务资讯公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看任务,删除任务操作,新增任务操作,修改任务操作。任务资讯公告信息管理页面提供的功能操作有:新增任务资讯公告,修改任务资讯公告,删除任务资讯公告操作。任务资讯公告类型管理页面显示所有任务资讯公告类型,在此页面既可以让管理员添加新的任务资讯公告信息类型,也能对已有的任务资讯公告类型信息执行编辑更新,失效的任务资讯公告类型信息也能让管理员快速删除。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值