APIO2010 T2 巡逻

转载:

http://blog.csdn.net/whjpji/article/details/7388271

http://blog.sina.com.cn/s/blog_86942b1401013vnx.html


巡逻 
【问题描述】 
在一个地区中有 n个村庄,编号为1, 2, ..., n。有n – 1条道路连接着这些村庄,每条道路刚好连接两个村庄,从任何一个村庄,都可以通过这些道路到达其他任一个村庄。每条道路的长度均为 1个单位。 为保证该地区的安全,巡警车每天要到所有的道路上巡逻。警察局设在编号为1的村庄里,每天巡警车总是从警察局出发,最终又回到警察局。 下图表示一个有8个村庄的地区,其中村庄用圆表示(其中村庄 1用黑色的
圆表示),道路是连接这些圆的线段。为了遍历所有的道路,巡警车需要走的距离为14 个单位,每条道路都需要经过两次。 

为了减少总的巡逻距离,该地区准备在这些村庄之间建立 K 条新的道路,每条新道路可以连接任意两个村庄。两条新道路可以在同一个村庄会合或结束(见下面的图例(c) )。一条新道路甚至可以是一个环,即,其两端连接到同一个村庄。 由于资金有限,K 只能是 1或2。同时,为了不浪费资金,每天巡警车必须经过新建的道路正好一次。 下图给出了一些建立新道路的例子: 


在(a)中,新建了一条道路,总的距离是 11。在(b)中,新建了两条道路,总的巡逻距离是 10。在(c)中,新建了两条道路,但由于巡警车要经过每条新道路正好一次,总的距离变为了 15。 试编写一个程序,读取村庄间道路的信息和需要新建的道路数,计算出最佳的新建道路的方案使得总的巡逻距离最小,并输出这个最小的巡逻距离。 

【输入格式】 
第一行包含两个整数 n, K(1 ≤ K ≤ 2)。接下来 n – 1行,每行两个整数 a, b,
表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n)。 
【输出格式】 
输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离。
 
【样例输入1】 
8 1 
1 2 
3 1 
3 4 
5 3 
7 5 
8 5 
5 6 
【样例输出 1】 
11 

【样例输入2】 
8 2 
1 2 
3 1 
3 4 
5 3 
7 5 
8 5 
5 6 
【样例输出 2】 
10  

【样例输入3】 
5 2 
1 2 
2 3 
3 4 
4 5 
【样例输出 3】 

【数据范围】 
10%的数据中,n ≤ 1000,      K = 1; 
30%的数据中,K = 1; 
80%的数据中,每个村庄相邻的村庄数不超过 25; 
90%的数据中,每个村庄相邻的村庄数不超过 150; 
100%的数据中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。 


整个题目的意思就是给你一棵1为根的树,在树上搭枝,使遍历所有枝的距离最短。
刚开始时每根枝都要走2次,搭枝的目的就是让更多走2次的边只走1次
对于k等于1的情况,很容易想到是在树的直径端点上搭枝,ans:=所有枝*2-最长路+1;
但是对于k等于2的情况,当我们在1基础再要让更多的枝只走一次时,对于最优情况可能第一次的优化的枝又会走2次,比较难处理。
我们可以让第一次优化的边的边长变作其相反数,这样再次找最长路时我们不但可以尽量避开已经
优化了的枝,而且当最优值要经过这些枝时就会在最长路的基础上减去其边长,当在计算ans时就可以减去最长路,等价于加上其长,也就是将这条优化的路变为了走2次(即为不优化)。
这样方法就出来了
k=1 ans=(n-1)*2+1-len
k>1 ans=ans-len+1
len 为直径。
但是注意求2次最长路的做法不适用于带负权的树.
我们可以通过找2个较大的儿子,最大值就是所有点的最大2个儿子(不能计入负数)之和的最大值


【注】

k=1,如上所说,在树的直径上连边

k=2,为了让更多的边只走一次,那么尽量不会去走环上的边

本来环上的边已经优化了,如果两个环相交的话,那么公共部分将会走两次

公共部分变成了负优化,因此把他的权设为-1


代码:

program patrol;
{$m 9000000}
{$inline on}
uses math;
const maxn=100000;
type
  ty1=^ty2;
  ty2=record
    n,s:longint;
    next:ty1;
  end;

var
  n,k,i,x,y,ans,p1,p2,len:longint;
  first:array[0..maxn] of ty1;
  root:array[0..maxn] of longint;
  get:array[0..maxn] of boolean;
//==============================
procedure insert(x,y:longint);inline;
var
  p:ty1;
begin
  new(p);
  p^.n:=y;
  p^.s:=1;
  p^.next:=first[x];
  first[x]:=p;
end;
//==============================第一次求2次最长路找直径
procedure find(x,s,father:longint);
var
  p:ty1;
begin
  get[x]:=true;
  p:=first[x];
  while p<>nil do
  begin
    if not get[p^.n] then
    begin
      root[p^.n]:=x;
      find(p^.n,s+1,father);
    end;
    p:=p^.next;
  end;
  if len<s then
  begin
    len:=s;
    if father=1 then p1:=x else p2:=x;
  end;
end;
//==============================将直径上的边的权改为其相反数
procedure huisu(x:longint);
var
  father:longint;
  p:ty1;
begin
  if (x=p1)or(x=0) then exit;
  father:=root[x];
  p:=first[x];
  while p<>nil do
  begin
    if p^.n=father then
    begin
      p^.s:=-1;
      break;
    end;
    p:=p^.next;
  end;
  p:=first[father];
  while p<>nil do
  begin
    if p^.n=x then
    begin
      p^.s:=-1;
      break;
    end;
    p:=p^.next;
  end;
  huisu(father);
end;
//==============================遍历找直径(有负权)
function dfs(x:longint):longint;
var
  p:ty1;
  max1,max2,t:longint;
begin
  get[x]:=true;
  p:=first[x];
  max1:=0; max2:=0;
  while p<>nil do
  begin
    if not get[p^.n] then
    begin
      t:=dfs(p^.n)+p^.s;
      if t>max1 then
      begin
        max2:=max1;
        max1:=t;
      end else
      if t>max2 then max2:=t;
    end;
    p:=p^.next;
  end;
  if len<max1+max2 then len:=max1+max2;
  exit(max1);
end;
//==============================
begin
  assign(input,'patrol.in'); reset(input);
  assign(output,'patrol.out'); rewrite(output);
  read(n,k);
  for i:=1 to n-1 do
  begin
    read(x,y);
    insert(x,y);
    insert(y,x);
  end;
  find(1,0,1);
  fillchar(get,sizeof(get),false);
  len:=0;
  find(p1,0,p1);
  ans:=n shl 1-1-len;
  if k=1 then writeln(ans)
  else begin
    huisu(p2);
    fillchar(get,sizeof(get),false);
    len:=0;
    dfs(1);
    ans:=ans-len+1;
    writeln(ans);
  end;
  close(input); close(output);
end.


此题可以用树形动态规划解决。
状态:f[u][j][k]表示u这棵子树中,共有j条完整的链加上k / 2条伸出到其它树根的链(这样的链算半条)。
转移过程见程序注释。

Accode:

#include <cstdio>
#include <cstdlib>
#include <algorithm>

using std::max;
const char fi[] = "patrol.in";
const char fo[] = "patrol.out";
const int maxN = 100010;

struct Edge {int v; Edge *next;} *edge[maxN];
int f[maxN][3][2], tmp[3][2], n, K;

void init_file()
{
    freopen(fi, "r", stdin);
    freopen(fo, "w", stdout);
    return;
}

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

inline void insert(int u, int v)
{
    Edge *p = new Edge;
    p -> v = v;
    p -> next = edge[u];
    edge[u] = p;
    return;
}

void readdata()
{
    n = getint(); K = getint();
    for (int i = 1; i < n; ++i)
    {
        int u = getint(), v = getint();
        insert(u, v); insert(v, u);
    }
    return;
}

void DP(int u, int Last)
{
    for (Edge *p = edge[u]; p; p = p -> next)
    if (p -> v != Last)
    {
        int v = p -> v; DP(v, u);
        memcpy(tmp, f[u], sizeof tmp);
        for (int j = 0; j < K + 1; ++j)
        for (int k = 0; j + k < K + 1; ++k)
        {
            f[u][j + k][0] = max(f[u][j + k][0],
                f[v][j][0] + tmp[k][0]);
	//没有半条链的情况。
            f[u][j + k][1] = max(f[u][j + k][1],
                f[v][j][0] + tmp[k][1]);
	//有半条链但在这棵树本身的情况。
            f[u][j + k][1] = max(f[u][j + k][1],
                f[v][j][1] + tmp[k][0] + 1);
	//其子树中有半条链的情况
	//(这时子树的半条链的长度要计入总长度,
	//但不计入总链数)。
            if (j + k < K) f[u][j + k + 1][0] =
                max(f[u][j + k + 1][0],
                    f[v][j][1] + tmp[k][1] + 1);
	//子树中的半条链连到根(不一定是根,
	//也有可能是该树的其它节点)的情况,
	//此时链的总数要+1。
        }
    }
    return;
}

void work()
{
    DP(1, 0);
    printf("%d\n", ((n - 1) << 1) + K -
            max(f[1][1][0], f[1][K][0]));
	//结果等于总边数的2倍减去求得的最长链
	//(此链不用重复走,所以被减去)
	//再加上新添的边的条数
	//(新添的边必须被经过)。
    return;
}

int main()
{
    init_file();
    readdata();
    work();
    return 0;
}

第二次做:

#include <cstdio>
#include <cstdlib>
#include <string>
#include <algorithm>
#define max(a, b) ((a) > (b) ? (a) : (b))

const int maxN = 100010;
struct Edge{int v; Edge *next;} *edge[maxN];
int f[maxN][3][2], pf[3][2], n, K;

void Dp(int u, int Last)
{
    for (Edge *p = edge[u]; p; p = p -> next)
    if (p -> v != Last)
    {
        Dp(p -> v, u);
        int v = p -> v;
        memcpy(pf, f[u], sizeof pf);
        //注意这里不能用成局部变量,否则爆栈。 
        for (int j = 0; j < K + 1; ++j)
        for (int k = 0; j + k < K + 1; ++k)
        {
            f[u][j + k][0] = max(f[u][j + k][0],
                pf[j][0] + f[v][k][0]);
            f[u][j + k][1] = max(f[u][j + k][1],
                pf[j][1] + f[v][k][0]);
            f[u][j + k][1] = max(f[u][j + k][1],
                pf[j][0] + f[v][k][1] + 1);
            if (j + k < K) f[u][j + k + 1][0]
                = max(f[u][j + k + 1][0],
                pf[j][1] + f[v][k][1] + 1);
        }
    }
    return;
}

inline int getint()
{
    int res = 0; char tmp;
    while (!isdigit(tmp = getchar()));
    do res = (res << 3) + (res << 1) + tmp - '0';
    while (isdigit(tmp = getchar()));
    return res;
}

inline void Ins(int u, int v)
{
    Edge *p = new Edge; p -> v = v;
    p -> next = edge[u]; edge[u] = p;
    return;
}

int main()
{
    freopen("patrol.in", "r", stdin);
    freopen("patrol.out", "w", stdout);
    n = getint(); K = getint();
    for (int i = 1; i < n; ++i)
    {
        int u = getint(), v = getint();
        Ins(u, v); Ins(v, u);
    }
    Dp(1, 0);
    int ans = max(f[1][1][0], f[1][K][0]);
    printf("%d\n", (n << 1) + K - 2 - ans); //
    return 0;
}




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值