P2279 [HNOI2003]消防局的设立(树形dp 树的最小半径覆盖)

原题: https://www.luogu.org/problemnew/show/P2279

题意:

n个点的树,设一个点为消防局,那么离这个点距离2以内的点会被覆盖。问最少的消防局数量使整棵树被覆盖。

树形DP:

先维护好儿子再推父亲的树形dp。

dp[i][0~4]表示遍历到i点时的5种状态:

  1. dp[i][0]:已经延到i点,且还可以往上2长度
  2. dp[i][1]:已经延到i点,且还可以往上1长度
  3. dp[i][2]:已经延到i点,且还可以往上0长度
  4. dp[i][3]:还未到达i点,父亲节点需要往下延1长度
  5. dp[i][4]:还未到达i点,父亲节点需要往下延2长度

分析儿子状态对父亲状态的转移:

  1. dp[fa][0]:自己放一个消费局,所以儿子是什么状态都不用管了,dp[i][0~4]都可以。
  2. dp[fa][1]:保证有一个儿子的状态为dp[i][0],这个儿子可以延到其他儿子,但不能延到其他儿子的儿子,所以需要其它儿子的dp[i][0~3]。
  3. dp[fa][2]:保证有一个儿子的状态为dp[i][1],这个儿子不能延到其他儿子,所以需要其他儿子的dp[i][0~2]。
  4. dp[fa][3]:不考虑自身,但需要保证fa的儿子、孙子全被覆盖,所以dp[i][0~2]。
  5. dp[fa][4]:不考虑自身和儿子,需要所有孙子都覆盖,所以dp[i][0~3]。

状态转移方程:

  1. d p [ f ] [ 0 ] = 1 + i m i n ( d p [ i ] [ 0...4 ] ) dp[f][0]=1+\sum_imin(dp[i][0...4])
  2. d p [ f ] [ 1 ] = m i n ( d p [ i ] [ 0 ] + j ! = i m i n ( d p [ j ] [ 0...3 ] ) ) dp[f][1]=min(dp[i][0]+\sum_{j!=i}min(dp[j][0...3]))
  3. d p [ f ] [ 2 ] = m i n ( d p [ i ] [ 1 ] + j ! = i m i n ( d p [ j ] [ 0...2 ] ) ) dp[f][2]=min(dp[i][1]+\sum_{j!=i}min(dp[j][0...2]))
  4. d p [ f ] [ 3 ] = i m i n ( d p [ i ] [ 0..2 ] ) dp[f][3]=\sum_imin(dp[i][0..2])
  5. d p [ f ] [ 4 ] = i m i n ( d p [ i ] [ 0..3 ] ) dp[f][4]=\sum_imin(dp[i][0..3])

d p [ i ] [ 1 ] + j ! = i m i n ( d p [ j ] [ 0...2 ] ) dp[i][1]+\sum_{j!=i}min(dp[j][0...2]) 可以转化为:
d p [ i ] [ 1 ] d p [ i ] [ 0...2 ] + m i n ( d p [ j ] [ 0...2 ] ) \quad dp[i][1]-dp[i][0...2]+\sum min(dp[j][0...2])

到这里应该已经可以做了,但是因为很多项可以合并以优化时间复杂度。


S [ i ] [ j ] = min ( d p [ i ] [ 0... j ] ) S[i][j]=\min(dp[i][0...j])

  1. d p [ f ] [ 0 ] = 1 + i S [ i ] [ 4 ] dp[f][0]=1+\sum_i S[i][4]
  2. d p [ f ] [ 3 ] = i S [ i ] [ 2 ] dp[f][3]=\sum_i S[i][2]
  3. d p [ f ] [ 4 ] = i S [ i ] [ 3 ] dp[f][4]=\sum_i S[i][3]
  4. d p [ f ] [ 1 ] = i S [ i ] [ 3 ] + m i n ( d p [ i ] [ 0 ] S [ i ] [ 3 ] ) dp[f][1]=\sum_i S[i][3] +min(dp[i][0]-S[i][3])
  5. d p [ f ] [ 2 ] = i S [ i ] [ 2 ] + m i n ( d p [ i ] [ 1 ] S [ i ] [ 2 ] ) dp[f][2]=\sum_i S[i][2] +min(dp[i][1]-S[i][2])

可以发现S只需要234,而dp需要01,可以放在dp上一起使用。

#include<bits/stdc++.h>
using namespace std;

const int N = 1005, M = 2005;
int head[N], nex[M], to[M], now;

void add(int a, int b) {
    nex[++now] = head[a];
    head[a] = now;
    to[now] = b;
}
int dp[N][6];
void dfs(int p, int f) {
    int s2 = 0, s3 = 0, s4 = 0, sub03 = 1e9, sub12 = 1e9;

    for(int i = head[p]; ~i; i = nex[i]) {
        int u = to[i];
        if(u == f)
            continue;
        dfs(u, p);
        s2 += dp[u][2];
        s3 += dp[u][3];
        s4 += dp[u][4];
        sub03 = min(sub03, dp[u][0] - dp[u][3]);
        sub12 = min(sub12, dp[u][1] - dp[u][2]);
    }

    dp[p][0] = 1 + s4;
    dp[p][1] = s3 + sub03;
    dp[p][2] = s2 + sub12;
    dp[p][3] = s2;
    dp[p][4] = s3;

    // dp to S
    dp[p][2] = min(min(dp[p][0], dp[p][1]), dp[p][2]);
    dp[p][3] = min(dp[p][2], dp[p][3]);
    dp[p][4] = min(dp[p][3], dp[p][4]);
    return ;
}

int main() {
    memset(head, -1, sizeof head);
    int n;
    scanf("%d", &n);
    for(int i = 2; i <= n; i++) {
        int tmp;
        scanf("%d", &tmp);
        add(i, tmp);
        add(tmp, i);
    }
    dfs(1, -1);
    printf("%d\n", dp[1][2]);
}

贪心:

更简单、更有效、使用范围更广

对于当前还没有被遍历的点中的最低点 I I ,在 I I 的眼中其他的点只有: I I 的父亲, I I 的爷爷, I I 的兄弟。

显然选择 I I 的爷爷最优,不仅解决了这里的所有点,还可以再往上延两个点。

现在有两个问题:最低点的寻找、更新的维护。

尝试使用数组按照深度顺序存储,从前往后线性遍历。就解决了最低点的问题。

至于更新点的维护,开一个dis数组,代表某个点的子树中,前dis层被覆盖。对于 I I 点,标记其爷爷后,dis[爷爷]=2,dis[父亲]=1,dis[曾爷爷]=1,dis[曾曾爷爷]=0。

那么对于遍历其他点的时候,看看它父亲和爷爷的dis值即可判断是否被覆盖。

#include<bits/stdc++.h>
using namespace std;

const int N = 1005, M = 2005;
int head[N], nex[M], to[M], now;
void add(int a, int b) {
    nex[++now] = head[a];
    head[a] = now;
    to[now] = b;
}

int fa[N], deep[N], dis[N];
void dfs(int p, int f) {
    for(int i = head[p]; ~i; i = nex[i]) {
        int u = to[i];
        if(u == f)
            continue;
        fa[u] = p;
        deep[u] = deep[p] + 1;
        dfs(u, p);
    }
    return ;
}

int tmp[N];
bool cmp(const int a, const int b) {
    return deep[a] > deep[b];
}
int main() {
    memset(head, -1, sizeof head);
    memset(dis, -1, sizeof(dis));
    int n;
    scanf("%d", &n);
    for(int i = 2; i <= n; i++) {
        int tmp;
        scanf("%d", &tmp);
        add(i, tmp);
        add(tmp, i);
    }
    // 考虑超过root的情况
    fa[1] = 0;
    fa[0] = 0;
    deep[1] = 1;
    dfs(1, -1);

    for(int i = 1; i <= n; i++)
        tmp[i] = i;
    sort(tmp + 1, tmp + 1 + n, cmp);
    int ans = 0;
    for(int i = 1; i <= n; i++) {
        int p = tmp[i], f = fa[p], ff = fa[f], fff = fa[ff], ffff = fa[fff];
        if(dis[p] >= 0 || dis[f] >= 1 || dis[ff] >= 2)
            continue;
        ans++;
        dis[f] = max(dis[f], 1);
        dis[ff] = max(dis[ff], 2);
        dis[fff] = max(dis[fff], 1);
        dis[ffff] = max(dis[ffff], 0);
    }
    printf("%d\n", ans);
}

猜你喜欢

转载自blog.csdn.net/jk_chen_acmer/article/details/86735731
今日推荐