Hdu4582-DFS spanning tree VS Pku2152-Fire 一类平方级树形最小覆盖问题

Pku2152-Fire

       题意:某国N个城市,之间形成树型交通网络。现要在一些城市建消防站,要求:每个城市i要么有自己的消防站,要么离最近的有消防站的城市的距离不超过Di(各个城市的要求不尽相同)。同时,各城市建消防站的费用Wi已知,也不尽相同。问建设消防站的总费用。
       解法:树型动态规划。想到用DP来解题不难,难的是想出怎么来DP。对于状态的设计,我们大概能猜到这个应该用的是二维的状态,其中一维表示Dp过程中的当前城市i,另一维要么是i与某个消防站(某个算作最近的好了)的距离(距离可能hash一下),要么也是城市编号j,表示j建了消防站,而i到j的距离不超过Di。综合可能的Dp转移方案会发现这两种表示都让人感觉别扭,或者说觉得不能有效表达出一个确切的状态,同时让人担心复杂度。~
        好了,不啰嗦了,讲讲为什么第二种(就是我解题用的那种)是可行的。
        首先我们把这个无根的树随便设一个根变为有根树。dp[j][i]表示在j处建了消防站,满足以i为根的子树中的节点城市都有可用的消防站,且dis(i, j)<= Di的最小建设费用(不包括j的费用)。这里我们假设i依靠的是j(j为i提供消防站)。首先,我们要明白一点,对于某个城市,知道在Di范围内建了站就行,你不用管它最近的站在哪,另外,我们只要保证在Dp过程中不会错过最优解,我们在转移状态时抛弃那些一定不可能成为最优状态的子状态必然是可行的。状态转移方程为:dp[i][u] = sigma(min{dp[i][v], dp[j][v] + W[j]});其中v是u的儿子节点,意思是,如果Dis(i, v)<=Dv的话,dp[i][u]应加上dp[i][v],否则,v不能依靠i,只能找一个别的城市j来依靠,并且总费用(包括W[j])要是最小的。易见dp[][]的初始值应设为infinity。保证了局部最优性,dp[i][v] -> dp[i][u]是无可厚非的,关键是当u有多个儿子节点时,比如有v和v',如果转移时都加的是dp[j][v]+W[j],dp[j][v']+W[j],即两点时依靠同一点,那么W[j]不就重复加了吗?这个担心一点也不多余,它是实实在在存在的,可以很容易举出这样的例子。然而这样做又是可行的就是,这样计算出来的状态肯定不影响最优解的,这种状态就是为了转移方程的统一性而牺牲的非最优解状态。
      下面说明一下为什么不会影响最优解。出现这种情况有两种可能,一种j是u某个儿子树上的,二是j是u的父辈乃至祖先,但都有一个特点,v(v'或其他依靠j的u的儿子节点)到j的路径上必然经过u,并且j到u的距离比i到u的距离小,不然v们必然是可以依靠i的。此时你会发现在这种条件下,u也必然可以依靠j,因为j更近(不管i处还建不建站),所以dp[i][u]的费用不会比dp[j][u]更优,而且在dp[j][u]中必然不会出现某个W重复计算,首先W[j]不会,其他类似的j'(包括i),dis(j', u) >= dis(j, u)的肯定也不会。保证了有这样的一个有效的dp[j][u]就够了。
       另外,在计算min{dp[j][v] + W[j]}时,可以在边求dp[i][v]边算,降低复杂度。

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = 1002;
const int INF = 0x3f3f3f3f;

int n, W[N], D[N], fa[N], dis[N][N], dp[N][N], best[N];
vector<pair<int, int> > e[N];

void getDis(int rt, int u, int curDis) {
    dis[rt][u] = curDis;
    for (int i = 0; i != (int)e[u].size(); i++) {
        int v = e[u][i].first;
        if (v == fa[u]) continue;
        fa[v] = u;
        getDis(rt, v, curDis + e[u][i].second);
    }
}

void DP(int u) {
    for (int i = 0; i != (int)e[u].size(); i++) {
        int v = e[u][i].first;
        if (v == fa[u]) continue;
        DP(v);
    }

    best[u] = INF;
    for (int i = 0; i < n; i++) if (dis[i][u] <= D[u]) {
        dp[i][u] = 0;
        for (int j = 0; j != (int)e[u].size(); j++) {
            int v = e[u][j].first;
            if (v == fa[u]) continue;
            dp[i][u] += min(best[v], dp[i][v]);
        }
        best[u] = min(best[u], dp[i][u] + W[i]);
    }
}

int main() {
    int cas, u, v, w;
    scanf("%d", &cas);
    while (cas--) {
        for (int i = 0; i < N; i++) e[i].clear();
        memset(dp, 0x3f, sizeof(dp));

        scanf("%d", &n);
        for (int i = 0; i < n; i++) scanf("%d", &W[i]);
        for (int i = 0; i < n; i++) scanf("%d", &D[i]);
        for (int i = 1; i < n; i++) {
            scanf("%d%d%d", &u, &v, &w);
            u--, v--;
            e[u].push_back(make_pair(v, w));
            e[v].push_back(make_pair(u, w));
        }
        for (int i = n-1; i >= 0; i--) fa[i] = i, getDis(i, i, 0);
        DP(0);
        printf("%d\n", best[0]);
    }
    return 0;
}

Hdu4582-DFS spanning tree

题意:略

分析:对于每一个T-simple环的树边上的点的深度依次减小(或增大)的,且必然不会来自某点x的不同的子树,这为树形DP提供了条件。环可以用环中深度最大的点v和深度最小的点u来表示,对于v一样的环,我们明显只要处理最小的那个就行了,因为小环的树边都在大环里。然后我们可以看到,对于某个环,我们可以用v~u路径上的边来覆盖这个环,答案就是覆盖所以环的最小边数。进一步我们可以理解为用v~u路径上的边覆盖v点。然后我们可以看出,这题和上一题很相似了,我这里说它相似,不是指都是覆盖,而是覆盖某点v的其它点都必然是在v的附近,形成一个连通的块,这点才是使得这类题有DP解的关键部分。不过,上一题要覆盖所有的点,而这个题,可以有些点不处于环的底部,这种点要特别处理一下,细节部分见代码。在杭州现场比赛时根本不知道怎么做,赛后问了科大的神牛,结果没听懂,又没能好意思再问,现在终于会做了,好弱。

#include <vector>
#include <list>
#include <map>
#include <set>
#include <queue>
#include <deque>
#include <stack>
#include <bitset>
#include <algorithm>
#include <functional>
#include <numeric>
#include <utility>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <ctime>
#include <string>
#include <cstring>
using namespace std;

typedef double DB;
typedef unsigned int UI;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
typedef vector<int> VI;
typedef vector<bool> VB;
typedef vector<char> VC;
typedef vector<double> VD;
typedef vector<string> VS;
typedef vector<VI> VVI;
typedef vector<PII> VPII;

#define PB push_back
#define MP make_pair
#define CLR(a, x) memset(a, x, sizeof(a))
#define FOR(i, s, t) for (int i = (s); i < (int)(t); i++)
#define FORD(i, s, t) for (int i = (t) - 1; i >= (int)(s); i--)
#define VREP(i, v) for (int i = 0; i < (int)(v).size(); i++)
#define REP(i, t) for (int i = 0; i < (int)(t); i++)
#define REPD(i, t) for (int i = (t) - 1; i >= 0; i--)
#define REP2(i, j, n, m) for (int i = 0; i < (int)(n); i++) for (int j = 0; j < (int)(m); j++)
#define REP3(i, j, k, n, m, l) for (int i = 0; i < (int)(n); i++) for (int j = 0; j < (int)(m); j++) for (int k = 0; k < (int)(l); k++)
#define foreach(i, v) for (__typeof((v).begin()) i = (v).begin(); i != (v).end(); i++)

template <class T> inline void checkMin(T& a, T b) { if (b < a) a = b; }
template <class T> inline void checkMax(T& a, T b) { if (b > a) a = b; }
inline int& RD(int& x) { scanf("%d", &x); return x; }
inline int& RD(int& x, int& y) { scanf("%d%d", &x, &y); return x; }
inline int& RD(int& x, int& y, int& z) { scanf("%d%d%d", &x, &y, &z); return x; }
inline DB& RD(DB& x) { scanf("%lf", &x); return x; }
inline DB& RD(DB& x, DB& y) { scanf("%lf%lf", &x, &y); return x; }
inline DB& RD(DB& x, DB& y, DB& z) { scanf("%lf%lf%lf", &x, &y, &z); return x; }
inline void WT(int x) { printf("%d\n", x); }
inline void WT(int x, int y) { printf("%d %d\n", x, y); }
inline void WT(int x, int y, int z) { printf("%d %d %d\n", x, y, z); }

const int N = 2005;
const int INF = 0x3f3f3f3f;
const LL LINF = 1ll << 62;
const DB DINF = 1e20;
const int MOD = 1000000007;
const int dx[] = {-1, 0, 0, 1};
const int dy[] = {0, -1, 1, 0};

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

int en, head[N], fa[N], dep[N], top[N];
bool cir[N];
struct Edge { int to, next; } e[N * 2];

void dfsDep(int u) {
    for (int i = head[u]; i != -1; i = e[i].next) if (e[i].to != fa[u]) {
        fa[e[i].to] = u; dep[e[i].to] = dep[u] + 1;
        dfsDep(e[i].to);
    }
}

VI rely[N];
int dp[N][N], best[N];
void DP(int u) {
    for (int i = head[u]; i != -1; i = e[i].next) if (e[i].to != fa[u]) DP(e[i].to);

    best[u] = 0;
    for (int i = head[u]; i != -1; i = e[i].next) if (e[i].to != fa[u]) best[u] += best[e[i].to];
    if (cir[u]) best[u]++;
    for (int x = u; x != top[u]; x = fa[x]) {
        dp[u][x] = 0;
        for (int i = head[u]; i != -1; i = e[i].next) if (e[i].to != fa[u]) dp[u][x] += min(best[e[i].to], dp[e[i].to][x]);
        checkMin(best[u], dp[u][x] + 1);
    }
}

int main() {
    int n, m; while (RD(n, m)) {
        en = 0; CLR(head, -1);
        REP(i, n-1) {
            int u, v; RD(u, v); u--, v--;
            e[en] = (Edge) {v, head[u]}; head[u] = en++;
            e[en] = (Edge) {u, head[v]}; head[v] = en++;
        }

        dep[0] = 0; CLR(cir, false); CLR(top, 0);
        dfsDep(0);
        FOR(i, n-1, m) {
            int u, v; RD(u, v); u--, v--;
            if (dep[u] > dep[v]) swap(u, v);
            if (dep[u] > dep[top[v]]) top[v] = u; // top[v]表示v所在最小的环的顶部
            cir[v] = true; // 标记该点是不是环的底部
        }

        CLR(dp, 0x3f);
        DP(0);
        WT(best[0]);

    }
    return 0;
}


你可能感兴趣的:(Hdu4582-DFS spanning tree VS Pku2152-Fire 一类平方级树形最小覆盖问题)