☆【树型动态规划】巡逻

【问题描述】 
在一个地区中有 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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值