Even Number Addicts - 题解【动态规划/记忆化搜索】
题面
本题是Codeforces Global Round 22 的C题。原题链接见:C. Even Number Addicts。下面搬运一下题面:
Description
Alice and Bob are playing a game on a sequence \(a_1,a_2,…,a_n\) of length \(n\). They move in turns and Alice moves first.
In the turn of each player, he or she should select an integer and remove it from the sequence. The game ends when there is no integer left in the sequence.
Alice wins if the sum of her selected integers is even; otherwise, Bob wins.
Your task is to determine who will win the game, if both players play optimally.
Input
Each test contains multiple test cases. The first line contains an integer \(t (1≤t≤100)\) — the number of test cases. The following lines contain the description of each test case.
The first line of each test case contains an integer \(n\) \((1≤n≤100)\), indicating the length of the sequence.
The second line of each test case contains \(n\) integers \(a_1,a_2,…,a_n\) \((−10^9 ≤ a_i ≤ 10^9)\), indicating the elements of the sequence.
Output
For each test case, output "Alice"
(without quotes) if Alice wins and "Bob"
(without quotes) otherwise.
Example
input
4
3
1 3 5
4
1 3 5 7
4
1 2 3 4
4
10 20 30 40
output
Alice
Alice
Bob
Alice
Note
In the first and second test cases, Alice always selects two odd numbers, so the sum of her selected numbers is always even. Therefore, Alice always wins.
In the third test case, Bob has a winning strategy that he always selects a number with the same parity as Alice selects in her last turn. Therefore, Bob always wins.
In the fourth test case, Alice always selects two even numbers, so the sum of her selected numbers is always even. Therefore, Alice always wins.
大意
给定一组数,Alice和Bob轮流取数,直到取完为止,Alice先。若Alice手上的数和为偶数则Alice胜利,否则Bob胜利。假设他们非常聪明,总是选择对自己有利的策略,问该局谁会赢。
题解
一开始以为是个博弈论,后来发现是个动态规划……
首先,对于本题我们不需要关心具体的数是什么,只要关心每个数的奇偶性就行。因此,预先计算出这组数的奇数个数与偶数个数。计奇数个数为on
,偶数个数为en
,很显然的可以想到以下情况:若全偶数Alice必胜,若全奇数,则Alice能够拿到一半(向上取整)奇数,若拿到的个数为偶数则赢,否则输。
剩下的情况中,在每一轮选取数字中,一共会出现四种情况(用AB代表Alice与Bob,10代表奇偶):00,01,10,11。这样,剩下的奇数和偶数个数又构成了一个子问题。但是,我们发现,根据Alice取的数不同,ta手上的数奇偶性是会改变的。因此,我们定义dp[i][j][k]
代表有\(i\)个奇数\(j\)个偶数并且Alice手上数总和奇偶性为\(k\)(1代表奇数0代表偶数)时是否能胜利(1代表能,-1代表不能)。
由于两人都按照最优策略行事,因此在00、01情况中Bob一定会选择对他有利的操作,就是Alice必输的操作。10、11两种情况同理。因此可以得到转移方程
转移方程中,因为\(k\)只取0或1,因此奇偶性变换时只需要计算\(1-k\)就可以了。在Alice取走奇数时,Bob要在取奇数dp[i-2][j][1-k]
与取偶数dp[i-1][j-1][1-k]
中做出能让Alice输的选择,在前面的定义中赢为1输为-1,所以需要在这两个表达式中取最小值。取走偶数同理。同时Alice想赢,因此需要在取奇数与取偶数的结果中取max。
根据转移方程,可以采用自顶向下记忆化搜索的策略进行求解,使用一个map记录当前状态是否搜索过。因为map初始化为0,因此输赢用1与-1表示。
本题一共会有\(on \times en \times 2\)种情况,每组数据时间复杂度为\(O(n^2)\)。本题最多100组数据,每组数据\(n\)最大为100,时限2秒,可以通过。
代码
这里使用了tuple,这是一个和pair很像的东西。
/*
* @Author: AlexHoring
* @Date: 2022-09-30 22:59:37
*/
#include <bits/stdc++.h>
#define GRP(a) if(a!=0){int T;cin>>T;for(int C=1;C<=T;++C){solve(C);}}else{solve(0);}
#define FAST ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rrep(i,a,b) for(int i=a;i>=b;--i)
#define elif else if
#define mem(arr,val) memset(arr,val,sizeof(arr))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int n;
vector<int> a;
map<tuple<int, int, int>, int> dp;
int dfs(int on, int en, int ac) {
if (dp[make_tuple(on, en, ac)] != 0) {
return dp[make_tuple(on, en, ac)];
}
if (on == 0) {
if (ac != 0) {
dp[make_tuple(on, en, ac)] = -1;
return -1;
} else {
dp[make_tuple(on, en, ac)] = 1;
return 1;
}
}
if (en == 0) {
int num = on / 2 + on % 2;
if (num % 2 == 0) {
if (ac != 0) {
dp[make_tuple(on, en, ac)] = -1;
return -1;
} else {
dp[make_tuple(on, en, ac)] = 1;
return 1;
}
} else {
if (ac == 0) {
dp[make_tuple(on, en, ac)] = -1;
return -1;
} else {
dp[make_tuple(on, en, ac)] = 1;
return 1;
}
}
}
--on;
int b = dfs(on, en - 1, 1 - ac);
if (on != 0) {
b = min(b, dfs(on - 1, en, 1 - ac));
}
int ans = b;
++on;
--en;
b = dfs(on - 1, en, ac);
if (en != 0) {
b = min(b, dfs(on, en - 1, ac));
}
++en;
ans = max(ans, b);
dp[make_tuple(on, en, ac)] = ans;
return ans;
}
void solve(int C) {
dp.clear();
cin >> n;
a.resize(n + 1);
int on = 0, en = 0;
rep(i, 1, n) {
cin >> a[i];
if (a[i] % 2) {
++on;
} else {
++en;
}
}
int ans = dfs(on, en, 0);
if (ans == 1) {
cout << "Alice\n";
} else {
cout << "Bob\n";
}
}
int main() {
#ifdef AlexHoring
freopen("a.in", "r", stdin);
freopen("a.out", "w", stdout);
#endif
FAST;
GRP(10);
return 0;
}
/*
_ _ _ _
/\ | | | | | | (_)
/ \ | | _____ _| |__| | ___ _ __ _ _ __ __ _
/ /\ \ | |/ _ \ \/ / __ |/ _ \| '__| | '_ \ / _` |
/ ____ \| | __/> <| | | | (_) | | | | | | | (_| |
/_/ \_\_|\___/_/\_\_| |_|\___/|_| |_|_| |_|\__, |
__/ |
|___/
*/