转载:
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】
6
【数据范围】
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;
}