AtCoder 2305 [AGC010D] Decrementing(博弈)

problem

Luogu链接

solution

case1 a a a 中如果有 1 1 1 的存在,那么相当于第二步失效,最后的结果仅由 ∑ a i − 1 \sum a_i-1 ai1 的奇偶性决定(奇数先手赢,偶数后手赢)

奇数先手赢,偶数后手赢。

case2:如果 ∑ a i − 1 \sum a_i-1 ai1 为奇数,先手必胜。

这看起来就似乎是正确的。

首先因为偶数除以奇数还是偶数,奇数除以奇数还是奇数,所以如果 gcd ⁡ \gcd gcd 是奇数,对奇偶性不影响。

如果先手能维持这个奇偶性到最后,即保证每人每次只有第一步操作有效,那他就赢了。

先手只需任选一个偶数 − 1 -1 1,再算上初始局面必然包含的一个奇数(否则初始 gcd ⁡ \gcd gcd 不为 1 1 1),这样至少就有两个奇数。

  • 此时若序列长度 > 2 >2 >2,先手 − 1 -1 1 之后:
    • gcd ⁡ = 1 \gcd=1 gcd=1。后手再让任意一个数 − 1 -1 1,序列的 gcd ⁡ \gcd gcd 仍然为 1 1 1,后手的第二步等价于无效。
    • gcd ⁡ ≠ 1 \gcd\ne 1 gcd=1,但 gcd ⁡ \gcd gcd 也一定是奇数。除掉 gcd ⁡ \gcd gcd 后,出现 1 1 1,奇偶性未改变。后手仍然面临的奇偶性为偶。先手必胜。
  • 特殊情况是序列长度 = 2 =2 =2,先手 − 1 -1 1 之后:
    • 可能两数相等,那正好就直接赢了。
    • 要是不相等,并且 − 1 -1 1 之后出现了倍数关系,那没什么影响,显然一个是另一个的奇数倍。约去 gcd ⁡ \gcd gcd 后就是一个 1 1 1 加上一个奇数,后手面临的奇偶性仍然为偶,先手仍然获胜。

要想保证每人每次只能取一个也很简单,先手只需任选一个偶数减1,再算上初始局面必然包含的一个奇数(否则初始gcd不为1),这样就至少有两个奇数了。

case3:如果 ∑ i = 1 n a i − 1 \sum_{i=1}^na_i-1 i=1nai1 为偶数,且有 > 1 >1 >1 a i a_i ai 为奇数,则先手必败。

若有多个奇数,这一轮先手选择一个变为偶数, gcd ⁡ \gcd gcd 仍然只可能是奇数,下一轮后手就能将其重新变回偶数,使先手第二步无效。

case4:如果为偶数,先手肯定想要翻盘,唯一办法是 2 ∣ gcd ⁡ 2\mid \gcd 2gcd

如果 ∑ i = 1 n a i − 1 \sum_{i=1}^na_i-1 i=1nai1 为偶数,且只有一个奇数,先手一定操作这个奇数,那么这一轮至少能 / 2 /2 /2,这是唯一的翻盘机会。

考虑做完上述特判和操作后,将操作权递交给对手。

只有 case4 是需要模拟操作下去的 O ( n log ⁡ 2 w ) O(n\log^2w) O(nlog2w),其余三种情况一步判断输赢即可。

#include <bits/stdc++.h>
using namespace std;
#define maxn 100005
#define int long long
int n;
int a[maxn];

int gcd( int x, int y ) {
    if( ! y ) return x;
    else return gcd( y, x % y );
}

signed main() {
    scanf( "%lld", &n );
    for( int i = 1;i <= n;i ++ ) scanf( "%lld", &a[i] );
    bool op = 0;
    while( 1 ) {
        int cnt = 0, sum = 0, flag = 0;
        for( int i = 1;i <= n;i ++ )
            cnt += (a[i] & 1), sum += a[i] - 1, flag |= (a[i] == 1);
        if( sum & 1 ) break;
        if( cnt ^ 1 ) { op ^= 1; break; }
        if( flag ) { op ^= 1; break; }
        int d = 0;
        for( int i = 1;i <= n;i ++ ) {
            if( a[i] & 1 ) a[i] --;
            d = gcd( d, a[i] );
        }
        for( int i = 1;i <= n;i ++ ) a[i] /= d;
        op ^= 1;
    }
    if( ! op ) puts("First");
    else puts("Second");
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值