CF1738C Even Number Addicts 【DP+奇偶博弈、数学推理】

13 篇文章 0 订阅
7 篇文章 0 订阅

题目链接

Codeforces Global Round 22, Problem C
1738C Even Number Addicts

题目大意

给定长度为 n n n 的数列 a 1 , a 2 , … , a n a_1, a_2, \dots, a_n a1,a2,,an ,两名玩家 Alice 、 Bob 依次以最优策略从数列中取走一个数,Alice 先取,直至为空博弈结束。若 Alice 取走的所有数之和为Alice 胜利;若 Alice 取走的所有数之和为Bob 胜利。输入给定序列,请输出必胜玩家。


解法 1 DP

定义

  • 定义两个二维 DP(每个 DP 的第一维代表序列 a a a 中偶数 a i a_i ai 的个数,第二维代表 a a a 中奇数 a i a_i ai 的个数):
    • dpEven[maxn][maxn] :代表首名开始的玩家能否以最优策略得到偶数和,简称首名以偶胜
    • dpOdd[maxn][maxn] :代表首名开始的玩家能否以最优策略得到奇数和,简称首名以奇胜
const int maxn = 100 + 10;
// dp[i][j]: odd num == i, even num == j
// Whether the person who starts first can get an EVEN sum. (plays the optimal strategy)
bool dpEven[maxn][maxn];
// Whether the person who starts first can get an ODD sum. (plays the optimal strategy)
bool dpOdd[maxn][maxn]; 

一些实例:

// n == 1
dpEven[1][0] = false;   dpOdd[1][0] = true;
dpEven[0][1] = true;    dpOdd[0][1] = false;
// n == 2
dpEven[2][0] = false;   dpOdd[2][0] = true;
dpEven[1][1] = true;    dpOdd[1][1] = true;
dpEven[0][2] = true;    dpOdd[0][2] = false;
// n == 3
dpEven[3][0] = true;    dpOdd[3][0] = false;
dpEven[2][1] = false;   dpOdd[2][1] = true;
dpEven[1][2] = false;   dpOdd[1][2] = true;
dpEven[0][3] = true;    dpOdd[0][3] = false;

为理解方便,此处采用两个二维 DP 的定义方法,读者也可采用三维 DP 的定义方法。

初始状态

  • 不难得到当序列全为偶数时,首名以偶胜首名以奇胜
  • 当序列全为奇数时:
    • 若首名玩家从序列 a a a 中最终取走偶数个数,首名以偶胜首名以奇胜
    • 若首名玩家从序列 a a a 中最终取走奇数个数,首名以偶胜首名以奇胜
for (int tot = 1; tot < maxn; tot ++){
	dpEven[0][tot] = true;
	dpOdd[0][tot] = false;
	
	int aliceNum = (tot+1) / 2;
	if (aliceNum % 2 == 0){
	    dpEven[tot][0] = true;
	    dpOdd[tot][0] = false;
	}
	else {
	    dpEven[tot][0] = false;
	    dpOdd[tot][0] = true;
	}
}

状态转移

共识:从序列拿走一个偶数,剩余序列和的奇偶性不改变;拿走一个奇数,剩余序列和的奇偶性改变

我们不妨假设 Alice 已经从 n n n 个数字中拿走了一个数字,并把 Bob 当成首名玩家来分析在 n − 1 n-1 n1 个数字的序列中的 首名以偶胜首名以奇胜 状态,Bob 的胜负条件可以决定 Alice 是否胜负。此问题在上一次转移已得到求解。

  • 初始序列和为偶数,Bob 最终需要得到奇数和才能击败 Alice :
    • 无论 Alice 从序列 a a a 中拿走一个奇数还是偶数,若 Bob 在剩余数字中的 首名以奇胜 状态全为 ,则 Alice 的 首名以偶胜,反之为假。
    • 同时更新 Alice 的 首名以奇胜,无论 Alice 拿走一个奇数还是偶数,若 Bob 在剩余数字中的 首名以偶胜 状态全为 ,则 Alice 的 首名以奇胜,反之为假。
  • 初始序列和为奇数,Bob 最终需要得到偶数和才能击败 Alice :
    • 无论 Alice 从序列 a a a 中拿走一个奇数还是偶数,若 Bob 在剩余数字中的 首名以偶胜 状态全为 ,则 Alice 的 首名以偶胜,反之为假。
    • 同时更新 Alice 的 首名以奇胜,无论 Alice 拿走一个奇数还是偶数,若 Bob 在剩余数字中的 首名以奇胜 状态全为 ,则 Alice 的 首名以奇胜,反之为假。
for (int tot = 1; tot < maxn; tot ++){
    for (int k = 1; k < tot; k ++){
        // totSum == even, Bob need ODD sum to defeat Alice
        if ((tot-k) % 2 == 0){
            dpEven[tot-k][k] = !(dpOdd[tot-k-1][k] && dpOdd[tot-k][k-1]);
            dpOdd[tot-k][k] = !(dpEven[tot-k-1][k] && dpEven[tot-k][k-1]);
        }
        // totSum == odd, Bob need EVEN sum to defeat Alice
        else {
            dpEven[tot-k][k] = !(dpEven[tot-k-1][k] && dpEven[tot-k][k-1]);
            dpOdd[tot-k][k] = !(dpOdd[tot-k-1][k] && dpOdd[tot-k][k-1]);
        }
    }
}

代码

最终统计序列 a a a 中偶数 a i a_i ai 和奇数 a i a_i ai 分别出现的次数cntEvencntOdd,并判断 首名以偶胜 dpEven[cntOdd][cntEven] 的状态即可输出胜利者姓名。
代码如下:

#include <bits/stdc++.h>
using namespace std;
#define YN(x) cout << (x ? "Alice" : "Bob") << endl
#define Yn(x) cout << (x ? "Yes" : "No") << endl
#define yn(x) cout << (x ? "yes" : "no") << endl

const int maxn = 100 + 10;
// dp[i][j]: odd num == i, even num == j
// Whether the person who starts first can get an EVEN sum. (plays the optimal strategy)
bool dpEven[maxn][maxn];
// Whether the person who starts first can get an ODD sum. (plays the optimal strategy)
bool dpOdd[maxn][maxn];

void init()
{
    for (int tot = 1; tot < maxn; tot ++){
        dpEven[0][tot] = true;
        dpOdd[0][tot] = false;

        int aliceNum = (tot+1) / 2;
        if (aliceNum % 2 == 0){
            dpEven[tot][0] = true;
            dpOdd[tot][0] = false;
        }
        else {
            dpEven[tot][0] = false;
            dpOdd[tot][0] = true;
        }

        for (int k = 1; k < tot; k ++){
            // totSum == even, Bob need ODD sum to defeat Alice
            if ((tot-k) % 2 == 0){
                dpEven[tot-k][k] = !(dpOdd[tot-k-1][k] && dpOdd[tot-k][k-1]);
                dpOdd[tot-k][k] = !(dpEven[tot-k-1][k] && dpEven[tot-k][k-1]);
            }
            // totSum == odd, Bob need EVEN sum to defeat Alice
            else {
                dpEven[tot-k][k] = !(dpEven[tot-k-1][k] && dpEven[tot-k][k-1]);
                dpOdd[tot-k][k] = !(dpOdd[tot-k-1][k] && dpOdd[tot-k][k-1]);
            }
        }
    }
}

int main()
{
	init();
    int tt;     cin >> tt;
    while (tt --){
        int n; 		cin >> n;
        int cntOdd = 0, cntEven = 0;
        int x;      
        for (int i = 0; i < n; i ++){
            cin >> x;
            if (x & 1)  cntOdd ++;
            else        cntEven ++;
        }
        YN(dpEven[cntOdd][cntEven]);
    }
    return 0;
}

解法 2 数学推理

共识:奇数 + 奇数 = 偶数;奇数 + 偶数 = 奇数;偶数 + 偶数 = 偶数。

统计序列 a a a 中偶数 a i a_i ai 和奇数 a i a_i ai 分别出现的次数 e e e o o o,依次可确定下列几种情况,决定二人胜负态:

  1. o ≡ 2 ( m o d 4 ) o \equiv 2 \pmod 4 o2(mod4) 时, Bob 存在必胜态;

Bob 仅需保证每次所取数字与 Alice 所取数字奇偶性相同即可,这样可以使二者取走的奇数个数相同。若在此过程中 Alice 取走了最后一个偶数,奇数必然剩余偶个。随后,Alice 和 Bob 各自将选择剩余奇数的一半。最后,Alice 和 Bob 都拥有 o 2 \frac{o}{2} 2o 个奇数,Alice 和为奇数,Alice 败。

  1. o ≡ 3 ( m o d 4 ) o \equiv 3 \pmod 4 o3(mod4) 时, Alice 存在必胜态;

Alice 首先选择一个奇数,若 Bob 能从剩下的数字中取走偶个奇数,则 Bob 胜。从情况 1 中可知,Bob 必败。

  1. o ≡ 0 ( m o d 4 ) o \equiv 0 \pmod 4 o0(mod4) 时, Alice 存在必胜态;

Alice 首先选择一个偶数,在随后的操作中,Alice 仅需保证每次所取数字与 Bob 所取数字奇偶性相同即可,这样可以使二者取走的奇数个数相同。若在此过程中偶数被取完,奇数必然剩余偶个。随后,Alice 和 Bob 各自将选择剩余奇数的一半。最后,Alice 和 Bob 都拥有 o 2 \frac{o}{2} 2o 个奇数,Alice 和为偶数,Alice 胜。

  1. o ≡ 1 ( m o d 4 ) o \equiv 1 \pmod 4 o1(mod4) 时,先选择奇数的人败。

先选择奇数的人将会导致对手出现情况 3,对手必胜。博弈开始时,如果 e e e 为偶即 e ≡ 0 ( m o d 2 ) e \equiv 0 \pmod 2 e0(mod2),则 Bob 将会取走最后一个偶数,Alice 败; e e e 为奇即 e ≡ 1 ( m o d 2 ) e \equiv 1 \pmod 2 e1(mod2),则 Alice 胜。

代码如下:

#include <bits/stdc++.h>
using namespace std;
#define YN(x) cout << (x ? "Alice" : "Bob") << endl

int main()
{
    int tt;     
    cin >> tt;
    while (tt --){
        int n; 		
        cin >> n;
        int cntOdd = 0, cntEven = 0;
        int x;      
        for (int i = 0; i < n; i ++){
            cin >> x;
            if (x & 1)  cntOdd ++;
            else        cntEven ++;
        }
        
        bool ok = false;
        if (cntOdd % 4 == 2)		ok = false;
        else if (cntOdd % 4 == 3)	ok = true;
    	else if (cntOdd % 4 == 0)	ok = true;
    	else if (cntEven % 2 == 1)	ok = true;
    	
        YN(ok);
    }
    return 0;
}
  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sycamore_Ma

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值