洛谷 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\)。
接下来考虑有加边的情况。发现加的边无非分成三类:返祖边、横插边、下插边。我们一类一类讨论。
- 返祖边。返祖边会使得树中存在一个环。可以发现,这类边是没有意义的。设返祖边为\((x,y)\),则\(dp(x)\)一定等于0。这是因为既然要把边添加到\(x\),那\(x\)肯定是必败状态,我才会试图改变我必败的命运。而且既然当前已经走到了\(x\),说明之前肯定是从\(y\)不得不走下来的,即两个人都采取最优策略的情况下从\(y\)走下来的。所以经过边\((x,y)\)之后,两个人肯定也会“不得不”从\(y\)沿着树边重新走回\(x\)。这样就死循环了,仍然改变不了我必败的命运。
- 横插边。要想让横插边\((x,y)\)有意义,首先必须满足的条件是\(dp(x)=0\)且\(dp(y)=0\)。\(dp(x)=0\)的原因和返祖边类似,不再赘述。\(dp(y)=0\)的原因是,我要达到一个目的:我走完\((x,y)\)这条边之后,你是必败局面。
- 下插边。同上。
我们再来考虑一下如果想让Alice赢,我们要把边插到哪些点上。
-
Alice先手。
若\(dp(root)=1\),则不必加边。
若\(dp(root)=0\),则我希望当我把横插边或下插边插在某一个点\(x\)上时,\(x\)会从必败局面转变为必胜局面,而且这个变化最终会导致根节点从必败状态转变为必胜状态。思考后会发现,\(x\)是满足条件的点,当且仅当\(dp(x)=0\),且\(x\)的所有兄弟的\(dp\)值是1。不仅\(x\)是这样,\(x\)的爷爷也必须这样,\(x\)的爷爷的爷爷也必须这样,\(x\)的爷爷的爷爷的爷爷也必须这样……
-
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;
}