NOIP2018 D2T3 保卫王国

懵懂的女人 提交于 2019-12-03 11:21:55

学习自: @秦淮岸

Force

如果没有限制条件,那么这道题就跟战略游戏 \(or\) 没有上司的舞会是一样的,只需\(dp\)一次就够了,所以很容易想到一个 \(44pts\) 的暴力:对于每次询问,都跑一遍 \(dp\),其中让 \(a, b\) 两个点强制转移(放/放)即可。

Thoughts

无解情况

显然,一般来说都是有解的,除非:

  • 命令冲突,即\(a = b\) ,且 \(x \not= y\)
  • \((a, b)\) 有一条边,即存在父子关系,而两个都不能放,即 \(x = 0 \& y = 0\)

分部分

发现若用暴力,很多地方的 \(dp\) 显然都是相同的。

这道题核心是:相邻两个点上必须有一个点有守卫

那么我们考虑把每次询问,将整张图分成若干个好预处理的部分,每个部分分开处理,让每个部分内部符合条件,让每个部分交界处满足条件。因为每个部分互不干扰,所以可以相对取 \(min\) 最优解。

考虑简单的情况, \((a, b)\) 在一条链上:

PS:\((a, b)\) 是对称的,如果\(a\)在底下咱们\(swap\)一下就行了

那么如果\((a, b)\) 不在一条链上,我们可以把它看做两条链:

  1. \(a\)\(lca\)
  2. \(lca\)\(b\)

具体情况如下图

所以,一切 \((a, b)\) 询问我们都可以归为以上两种情况,我们只需要用预处理大法求出来每一个部分的最小花费,求和就行了!

注意,为了不让交点重复计算,我们可以让橙色部分不算\(a, b\)的花费,紫色部分不算\(lca\)的花费

蓝色部分

\(f[u][0 / 1]\) 表示以 \(u\) 为子树, \(u\) 不选 / 选 下面的最大价值

转移 :

\(f[u][1] = p[u]\)

\(f[u][0] += f[v\)][1]

\(f[u][1] += min(f[v][0], f[v][1])\)

这跟暴力、没有上司的舞会 / 战略游戏就是一样的嘛。

紫色部分

\(g[u][0 / 1]\) 表示除了 \(u\) 的子树,剩下的 不选 / 选 下面的最大价值

转移:

\(g[v][1] = p[v]\)

\(g[v][0] = g[u][1] + f[u][1] - min(f[v][0], f[v][1]);\)

$g[v][1] = min(g[v][0], g[u][0] + f[u][0] - f[v][1]); $

这个思想类似于 1073. 树的中心,从父亲向儿子 \(dp\)

橙色部分

因为不论怎样我们都要搞 \(lca\),所以我们不妨边倍增跳,边求解。顺便维护倍增最小花费。

$w[u][0 / 1][i][0 / 1] $

\(z = u\) 往上跳 \(2 ^ i\) 步的点

\(u\) 不选/选;\(z\)不选 / 选。 以 \(z\) 的子树中,不包含 \(u\) 子树的最小花费。

初始化,这里的转移由蓝色部分逆推回去,既然 \(f[u][1 / 0]\)代表以 \(u\)为子树的最小花费,我再减掉相应 \(v\) 的贡献不就行了吗?

\(w[v][0][0][0] = -inf\)

\(w[v][0][0][1] = f[u][1] - min(f[v][0], f[v][1])\)

\(w[v][1][0][0] = f[u][0] - f[v\)][1]

\(w[v][1][0][1] = min(w[v][0][0][1], w[v][1][0][0]);\)

倍增,枚举中间点选不选:

\(w[u][x][i][y] = min(w[u][x][i][y], w[u][x][i - 1][z] + w[fa[u][i - 1]][z][i - 1][y]); (0 <= x, y, z <= 1)\)

最后\(dp\)求解,与倍增数组 \(w\) 的处理方式一样,倍增跳即可,注意,\(LCA\)分选 \(or\) 不选两种情况取 \(min\)

时间复杂度

预处理 \(O(Nlog_2N)\),每次询问 \(O(Mlog_2N)\)

故总复杂度 \(O((N + M)log_2N)\)

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100005, L = 18;

int n, m, p[N];
char type[10];
int head[N], numE = 0;
LL f[N][2], g[N][2], w[N][2][L][2];
int fa[N][L], dep[N];
struct E{
    int next, v;
}e[N << 1];
void add(int u, int v) {
    e[++numE] = (E) { head[u], v };
    head[u] = numE;
}

void dfs0(int u) {
    f[u][1] = p[u];
    for (int i = 1; i < L && fa[u][i - 1]; i++){
        fa[u][i] = fa[fa[u][i - 1]][i - 1];
    }
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].v;
        if(v == fa[u][0]) continue;
        dep[v] = dep[u] + 1;
        fa[v][0] = u;
        dfs0(v);
        f[u][0] += f[v][1];
        f[u][1] += min(f[v][0], f[v][1]);

    }
} 
void dfs2(int u) {
    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].v;
        if(v == fa[u][0]) continue;

        w[v][0][0][1] = f[u][1] - min(f[v][0], f[v][1]);
        w[v][1][0][0] = f[u][0] - f[v][1];
        w[v][1][0][1] = w[v][0][0][1];
        dfs2(v);
    }
}
void dfs1(int u) {

    for (int i = 1; i < L && fa[fa[u][i - 1]][i - 1]; i++) {
        for (int x = 0; x <= 1; x++) {
            for (int y = 0; y <= 1; y++) {

                for (int z = 0; z <= 1; z++) {
                    w[u][x][i][y] = min(w[u][x][i][y], w[u][x][i - 1][z] + w[fa[u][i - 1]][z][i - 1][y]);
                }
            }
        }
    }

    for (int i = head[u]; i; i = e[i].next) {
        int v = e[i].v;
        if(v == fa[u][0]) continue;

        g[v][0] = g[u][1] + f[u][1] - min(f[v][0], f[v][1]);
        g[v][1] = min(g[v][0], g[u][0] + f[u][0] - f[v][1]);
        dfs1(v);
    }
}

LL inline dp(int a, int x, int b, int y) {
    LL nx[2], ny[2], tx[2], ty[2];
    // nx, ny 表示从开始跳到当前的 a, b 点,这个点放不放军队的最小花费
    if(dep[a] < dep[b]) {
        swap(x, y); swap(a, b);
    }
    // 先跳到同一深度
    memset(tx, 0x3f, sizeof tx);
    memset(ty, 0x3f, sizeof ty);
    tx[x] = f[a][x], ty[y] = f[b][y];
    for (int i = L - 1; ~i; i--) {
        if(dep[a] - (1 << i) < dep[b]) continue;
        memset(nx, 0x3f, sizeof nx);
        for (int u = 0; u <= 1; u++) {
            // u:上一个选不选;v 这一个选不选
            for (int v = 0; v <= 1; v++) {
                nx[v] = min(nx[v], tx[u] + w[a][u][i][v]);
            }
        }
        memcpy(tx, nx, sizeof nx);
        a = fa[a][i];
    }
        
    // 一条链的情况直接返回
    if(a == b) return tx[y] + g[b][y];
        
  
    // 一起挖往上跳
    for (int i = L - 1; ~i; i--) {
        if(fa[a][i] == fa[b][i]) continue;
        memset(nx, 0x3f, sizeof nx);
        memset(ny, 0x3f, sizeof ny);
        for (int u = 0; u <= 1; u++) {
            for (int v = 0; v <= 1; v++) {
                // u:上一个选不选;v 这一个选不选
                nx[v] = min(nx[v], tx[u] + w[a][u][i][v]);
                ny[v] = min(ny[v], ty[u] + w[b][u][i][v]);
            }
        }
        memcpy(tx, nx, sizeof nx);
        memcpy(ty, ny, sizeof ny);

        a = fa[a][i];
        b = fa[b][i];

    }
    int lca = fa[a][0];
    // res0:不选 lca; res1:选lca
    LL res0 = g[lca][0] + f[lca][0] - f[a][1] - f[b][1] + tx[1] + ty[1];
    LL res1 = g[lca][1] + f[lca][1] - min(f[a][1], f[a][0]) - min(f[b][0], f[b][1]) + min(tx[1], tx[0]) + min(ty[1], ty[0]);
    return min(res0, res1);
}

int main(){
    // 读入
    memset(w, 0x3f, sizeof w);
    scanf("%d%d%s", &n, &m, type);
    for (int i = 1; i <= n; i++) scanf("%d", p + i);
    for (int i = 1, u, v; i < n; i++) {
        scanf("%d%d", &u, &v);
        add(u, v); add(v, u);
    }
    dep[1] = 1;
    // 处理 dep, fa, f 数组
    dfs0(1);
    // 初始化 w 数组
    dfs2(1);
    // 倍增 w 数组 + 处理 g 数组
    dfs1(1);
  
    // 询问
    for (int i = 1, a, x, b, y; i <= m; i++) {
        scanf("%d%d%d%d", &a, &x, &b, &y);
        // 无解判断:仅当 x -> y 存在一条边且两个都不让放,就GG了
        if(!x && !y && (fa[a][0] == b || fa[b][0] == a)) {
            puts("-1");
        } else {
            printf("%lld\n", dp(a, x, b, y));
        }
    }
    return 0;
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!