洛谷 P7443 加边 题解

一、题目:

洛谷原题

二、思路:

这题算是我想出来的吧,庆祝一下!

首先思考如果没有加边的情况该怎么做,那自然是一个典型的“有向图游戏”,用一遍树形DP即可解决。

具体来说,设某一方现在处于点\(x\),下一步轮到他走,则\(dp(x)=1\)表示他必胜,\(dp(x)=0\)表示他必败。

\(\forall y \in son(x)\),都有\(dp(y)=1\),那么\(dp(x)=0\)

\(\exists y\in son(x)\),满足\(dp(y)=0\),那么\(dp(x)=1\)

接下来考虑有加边的情况。发现加的边无非分成三类:返祖边、横插边、下插边。我们一类一类讨论。

  1. 返祖边。返祖边会使得树中存在一个环。可以发现,这类边是没有意义的。设返祖边为\((x,y)\),则\(dp(x)\)一定等于0。这是因为既然要把边添加到\(x\),那\(x\)肯定是必败状态,我才会试图改变我必败的命运。而且既然当前已经走到了\(x\),说明之前肯定是从\(y\)不得不走下来的,即两个人都采取最优策略的情况下从\(y\)走下来的。所以经过边\((x,y)\)之后,两个人肯定也会“不得不”\(y\)沿着树边重新走回\(x\)。这样就死循环了,仍然改变不了我必败的命运。
  2. 横插边。要想让横插边\((x,y)\)有意义,首先必须满足的条件是\(dp(x)=0\)\(dp(y)=0\)\(dp(x)=0\)的原因和返祖边类似,不再赘述。\(dp(y)=0\)的原因是,我要达到一个目的:我走完\((x,y)\)这条边之后,你是必败局面。
  3. 下插边。同上。

我们再来考虑一下如果想让Alice赢,我们要把边插到哪些点上。

  1. Alice先手。

    \(dp(root)=1\),则不必加边。

    \(dp(root)=0\),则我希望当我把横插边或下插边插在某一个点\(x\)上时,\(x\)会从必败局面转变为必胜局面,而且这个变化最终会导致根节点从必败状态转变为必胜状态。思考后会发现,\(x\)是满足条件的点,当且仅当\(dp(x)=0\),且\(x\)的所有兄弟的\(dp\)值是1。不仅\(x\)是这样,\(x\)的爷爷也必须这样,\(x\)的爷爷的爷爷也必须这样,\(x\)的爷爷的爷爷的爷爷也必须这样……

  2. Alice后手。

    \(dp(root)=0\),则不必加边。

    \(dp(root)=1\),同“Alice先手”。

把这些点标记后,对于某一个被标记的点\(x\),我们只需找出除了\(x\)祖先之外的、满足dp值是0的点中,点权最小的那一个。更新答案即可。

考虑这个东西怎么找。

我们维护一个全局变量\(minn\)。在访问完一个点\(x\)后,再用\(x\)的点权更新\(minn\)。正反两次dp即可。这不是我们强调的重点。

时间复杂度\(O(n)\)

三、代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>

using namespace std;

#define int long long // 嘿嘿嘿
#define mem(s, v) memset(s, v, sizeof s)

inline int read(void) {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
    return f * x;
}

const int maxn = 2e5 + 5, inf = 2e18 + 1;

int T, n, t, A, B;
int a[maxn], ans;
bool tag[maxn];
bool valid[maxn];
int minn;

vector<int>linker[maxn];

inline void clear(void) {
    mem(tag, 0);
    for (int i = 1; i <= n; ++ i) {
        linker[i].clear();
    }
    mem(valid, 0);
    ans = inf;
    minn = inf;
}

void dfs(int x) {
    tag[x] = false;
    for (int i = 0; i < (int)linker[x].size(); ++ i) {
        int y = linker[x][i];
        dfs(y);
        if (!tag[y]) tag[x] = true;
    }
}

void dfs2(int x, int fa) {
    int cnt = 0, pos = 0;
    for (int i = 0; i < (int)linker[x].size(); ++ i) {
        int y = linker[x][i];
        if (!tag[y]) {
            ++ cnt;
            pos = y;
        }
    }
    if (cnt == 1 && valid[fa]) valid[pos] = true;
    for (int i = 0; i < (int)linker[x].size(); ++ i) {
        int y = linker[x][i];
        dfs2(y, x);
    }
}

void dfs3(int x) {
    for (int i = 0; i < (int)linker[x].size(); ++ i) {
        int y = linker[x][i];
        dfs3(y);
    }
    if (minn != inf && valid[x]) ans = min(ans, A * a[x] + B * minn);
    if (!tag[x]) minn = min(minn, a[x]);
}

void dfs4(int x) {
    for (int i = (int)linker[x].size() - 1; i >= 0; -- i) {
        int y = linker[x][i];
        dfs4(y);
    }
    if (minn != inf && valid[x]) ans = min(ans, A * a[x] + B * minn);
    if (!tag[x]) minn = min(minn, a[x]);
}

signed main() {
    T = read();
    while (T --) {
        n = read(); t = read(); A = read(); B = read();
        clear();
        for (int i = 2; i <= n; ++ i) {
            int x = read();
            linker[x].push_back(i);
        }
        for (int i = 1; i <= n; ++ i) {
            a[i] = read();
        }
        dfs(1);
        if (t == 0) {
            if (tag[1]) { puts("0"); continue; }
            valid[1] = true;
            dfs2(1, 0);
            dfs3(1);
            minn = inf;
            dfs4(1);
            printf("%lld\n", ans == inf ? -1 : ans);
        }
        else {
            if (!tag[1]) { puts("0"); continue; }
            valid[0] = true;
            dfs2(1, 0);
            dfs3(1);
            minn = inf;
            dfs4(1);
            printf("%lld\n", ans == inf ? -1 : ans);
        }
    }
    return 0;
}

posted @ 2021-03-22 18:58  蓝田日暖玉生烟  阅读(48)  评论(0编辑  收藏  举报