【问题描述】
在一个地区中有 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。
此题可以用树形动态规划解决。状态: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;
}