【CodeForces457F】An easy problem about trees

题目链接

点击打开链接

题目解法

不难想到二分答案,二分答案后,问题转化为了权值均为 0 / 1 0/1 0/1 的版本。

我们称一棵非叶节点数为奇数的树为奇树,非叶节点数为偶数的树为偶树。

对于一棵奇树,先手玩家拥有最后一次操作的权利,因此,奇树先手胜,当且仅当先手可以让两棵子树中的至少一棵变为己方权值。

考虑一棵奇树拥有两棵偶子树的情况,此时先手胜当且仅当在任意一颗偶子树内先手胜。这是因为当后者成立时,先手玩家可以先在该子树中操作,若后手玩家操作另一棵子树,则同样操作那棵子树,由于两棵子树均为偶树,因此先手玩家可以确保一颗子树的结果为己方权值。同样,当条件不满足时,后手玩家只需要操作先手玩家上次操作的子树即可获胜。

由类似的考虑,我们希望得出一个树上自底向上的动态规划做法来解题。

但有时情况并不简单,考虑一棵奇树拥有两棵奇子树的情况,尽管先手玩家可以在一棵单独的子树里获胜,但后手玩家可以通过操作另一颗子树来迫使先手玩家在能够得胜的子树中继续操作,从而破坏胜局。这种情况下,我们可以认为后手玩家跳过了一次他的回合。事实上,在这个情况内,如果我们选定一个子树进行观察,先手玩家同样有机会选择跳过他的一个回合,因此,我们需要在状态中增设一个变量 s k i p skip skip ,表示是否有玩家可以跳过他的回合。对于一棵奇树拥有两棵奇子树的情况,先手玩家胜当且仅当他能够在其中一个子树的 s k i p = 1 skip=1 skip=1 的情况下获胜。

可以发现的是,多次跳过回合是没有意义的,因为对方玩家同样可能进行跳过,而使我方上次的跳过失效,因此,只需要记录可以跳过的回合数的奇偶性,也就是有或者没有。

为了方便下文的描述,我们规定 A A A 能够在某子树 s k i p = x skip=x skip=x 的情况下获胜指在 A A A 先手时,能够在 s k i p = x skip=x skip=x 的规则下,最终将子树变为己方权值。其中 s k i p = 0 skip=0 skip=0 表示不能跳过回合, s k i p = 1 skip=1 skip=1 表示有一个玩家可以跳过一个回合, s k i p = 2 skip=2 skip=2 表示有一个玩家必须跳过一个回合,出现 s k i p = 2 skip=2 skip=2 的情况是因为一些边界情况(存在子树操作次数在 1 1 1 以内)所造成的,有关内容请读者阅读代码,题解中仅描述非边界情况(不然要说的实在太多了)。

以下论述中, A A A 为先手玩家,当前节点的两个子树 L , R L,R L,R 操作次数均 ≥ 2 \geq2 2 s k i p ∈ { 0 , 1 } skip\in\{0,1\} skip{0,1}

1 ​ 1​ 1 、两棵子树均为偶树, s k i p = 0 ​ skip=0​ skip=0 :由上文的论述, A ​ A​ A 能获胜当且仅当 A ​ A​ A 能在 L , R ​ L,R​ L,R 中的至少一个 s k i p = 0 ​ skip=0​ skip=0 的情况下获胜。

2 2 2 、两棵子树均为奇树, s k i p = 0 skip=0 skip=0 :由上文的论述, A A A 能获胜当且仅当 A A A 能在 L , R L,R L,R 中的至少一个 s k i p = 1 skip=1 skip=1 的情况下获胜。

3 3 3 L L L 为奇树, R R R 为偶树, s k i p = 0 skip=0 skip=0 :考虑 A A A 的第一步行动在 L L L 中还是在 R R R 中。
( 1 ) (1) (1) 、若在 L L L 中, 则对于 B B B 而言剩下了情况 1 1 1 A A A 获胜当且仅当 A A A L L L s k i p = 0 skip=0 skip=0 获胜, B B B R R R s k i p = 0 skip=0 skip=0 不能获胜。
( 2 ) (2) (2) 、若在 R R R 中, 则对于 B B B 而言剩下了情况 2 2 2 A A A 获胜当且仅当 A A A R R R s k i p = 1 skip=1 skip=1 获胜, B B B L L L s k i p = 1 skip=1 skip=1 不能获胜。
A A A 可以在两种情况中做出选择。

对于 s k i p = 1 skip=1 skip=1 的情况, A A A 首先可以考虑直接跳过,若 B B B 无法在剩余局面中获胜,则 A A A 可以通过跳过获胜。

4 4 4 L L L 为奇树, R R R 为偶树, s k i p = 1 skip=1 skip=1 :此时如果 A A A 可以在 s k i p = 0 skip=0 skip=0 时赢下 R R R ,或在 s k i p = 1 skip=1 skip=1 时赢下 L L L A A A 都可以通过有意地进行一次跳过(注意边界情况),来获得最后一步,从而取胜。若以上两点均不成立,那么 B B B 便可以确保 A A A 不在任意一棵子树中获胜。

5 5 5 、两棵子树均为偶树, s k i p = 1 skip=1 skip=1 :在进行一次操作后,对于 B B B 而言将剩下了情况 4 4 4 ,枚举所操作的子树即可。

6 6 6 、两棵子树均为奇树, s k i p = 1 skip=1 skip=1 :在进行一次操作后,对于 B B B 而言同样剩下了情况 4 4 4 ,枚举所操作的子树即可。

边界情况请读者自行讨论吧。

时间复杂度 O ( N L o g V ) O(NLogV) O(NLogV)

实际上观察写出的代码可以将位运算改为 M i n / M a x Min/Max Min/Max 运算而避免二分,从而一次 d p dp dp 得到答案,时间复杂度降为 O ( N ) O(N) O(N)

#include
using namespace std;
const int MAXN = 255;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template  void chkmax(T &x, T y) {x = max(x, y); }
template  void chkmin(T &x, T y) {x = min(x, y); } 
template  void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template  void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template  void writeln(T x) {
	write(x);
	puts("");
}
int n, mid, size[MAXN], val[MAXN], l[MAXN], r[MAXN];
bool dp[MAXN][2][3], vis[MAXN][2][3];
bool merge(bool x, bool y, bool who) {
	if (x == who || y == who) return who;
	else return !who;
}
// who: 0 -> Min, 1 -> Max
// skip: 0 -> none, 1 -> can, 2 -> must
bool getdp(int pos, bool who, int skip) {
	if (val[pos] != -1) {
		return val[pos] > mid;
	} else if (size[pos] == 1) {
		return merge(getdp(l[pos], 0, 0), getdp(r[pos], 0, 0), who ^ (skip == 2));
	} else if (vis[pos][who][skip]) {
		return dp[pos][who][skip];
	} else {
		bool &ans = dp[pos][who][skip];
		vis[pos][who][skip] = true;
		if (skip) {
			ans = getdp(pos, !who, 0);
			if (size[pos] & 1) {
				if (size[l[pos]] & 1) {
					ans = merge(ans, merge(getdp(l[pos], who, 0), getdp(r[pos], !who, 1 + (size[l[pos]] == 1 && skip == 1)), !who), who);
					ans = merge(ans, merge(getdp(r[pos], who, 0), getdp(l[pos], !who, 1 + (size[r[pos]] == 1 && skip == 1)), !who), who);
				} else {
					if (size[l[pos]]) {
						ans = merge(ans, merge(getdp(l[pos], who, 1 + (skip == 1 && size[r[pos]] == 0)), getdp(r[pos], !who, 0), !who), who);
					}
					if (size[r[pos]]) {
						ans = merge(ans, merge(getdp(r[pos], who, 1 + (skip == 1 && size[l[pos]] == 0)), getdp(l[pos], !who, 0), !who), who);
					}
				}
			} else {
				ans = merge(ans, merge(getdp(l[pos], who, 1 + (skip == 1 && size[r[pos]] == 0)), getdp(r[pos], who, 0), who), who);
			}
		} else {
			if (size[pos] & 1) {
				if (size[l[pos]] & 1) {
					ans = merge(getdp(l[pos], who, 1), getdp(r[pos], who, 1), who);
				} else {
					ans = merge(getdp(l[pos], who, 0), getdp(r[pos], who, 0), who);
				}
			} else {
				ans = merge(merge(getdp(l[pos], who, 0), getdp(r[pos], !who, 0), !who), 
				            merge(getdp(r[pos], who, 1), getdp(l[pos], !who, 1), !who), who);
			}
		}
		return ans;
	}
}
void dfs(int pos) {
	size[pos] = 0;
	if (val[pos] != -1) return;
	dfs(l[pos]), dfs(r[pos]);
	size[pos] = 1 + size[l[pos]] + size[r[pos]];
	if (size[r[pos]] & 1) swap(l[pos], r[pos]);
}
void init(int n) {
	for (int i = 0; i <= n - 1; i++) {
		read(val[i]);
		if (val[i] == -1) {
			read(l[i]);
			read(r[i]);
		} else l[i] = r[i] = -1;
	}
	dfs(0);
}
int main() {
	int T; read(T);
	while (T--) {
		read(n), init(n);
		int l = 0, r = 1e3;
		while (l < r) {
			mid = (l + r) / 2;
			memset(vis, false, sizeof(vis));
			if (getdp(0, 1, 0)) l = mid + 1;
			else r = mid;
		}
		writeln(l);
	}
	return 0;
}

你可能感兴趣的:(【资料】神仙题,【算法】动态规划,【算法】博弈论)