P8901 [USACO22DEC] Circular Barn S 题解

前言

这道题用到 质数筛博弈论

不错的博弈论入门题,发篇题解加深印象(此做法为官方同款做法,时间复杂度为 $O(n)$ )。

这里是 官方题解

没学过博弈论的蒟蒻在场上懵了。。。而且在洛谷提交的时候还中毒了

以此提醒:函数没有返回值千万别不用 void 。

题意简化

两个人对决,有 $n$ 个房间,每个房间的值为 $a_i$ ,两人总是在同个房间,且初始在 $1$ 号房间。之后每次操作两人从 Farmer John 开始把这个房间的值各减去一个质数或 $1$ ,然后进入下一个房间(房间 $n$ 的下一个房间是房间 $1$ ),知道某个时刻某个人拿到的数是 $0$ 位置,那个人输了,另一个人赢了,请你输出赢的那个人的名字( Farmer John 和 Farmer Nhoj 之一)。

思路

像这类题目,我们可以先从 $n=1$ 入手。 $n=1$ 的情况很简单,其实就是分类讨论:

  1. $n/4$ 余 $0$ 。这种情况的话 后手必胜 。原因是先手能减去的数中除以 $4$ 的余数必定为 $1$ , $2$ , $3$ 之一(因为 $4$ 的倍数不可能是质数,也不是 $1$ )。
  2. $n/4$ 余 $1$ , $2$ 或 $3$ 。这种情况 先手必胜 。原因是先手可以减去一个数使得其边为 $4$ 的倍数然后丢给后手(先手可以减去 $1$ ,$2$ ,$3$ 之一),就变成上面一种情况了。

再考虑到 $n>1$ 的情况。考虑对于一个房间,那个赢的人必定想缩短操作次数(原因是对于这个房间可以尽快赢,以防止到后面的房间让输的人翻盘),输的人想使操作次数越多越好(原因是这个房间操作次数多了,结束的时间会拖延,以给后面的房间制造机会),所以我们只要按照双方的策略算出操作次数,取最早结束的那个房间即可。

接下来是怎样快速地算一个房间会操作几次(一人操作算一次)。我们继续分类讨论:

  1. $a_i$ 为 $4$ 的倍数。这种情况会操作 $a_i/2$ 次。原因比较简单,先手每次减去 $2$ ,这样的话后手只能减 $2$ ,每人每次减 $2$ 。
  2. $a_i$ 不为 $4$ 的倍数。那么先手想尽快结束,所以就是 $(a_i-p)/2+1$ 次操作( $p$ 是小于等于 $p$ 最大的质数)。

接下来对每个房间的 操作次数的一半(即回合数) 取最小值,一样按房间顺序取,然后判断取到的那个房间的操作次数是奇数还是偶数来判定结果。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10,M=5e6;
int T,n,x,cnt,ans,mx[4],v[M+10],vis[M+10],prime[M];
void get_prime(int n){
    vis[1]=1;
    mx[1]=1;
    v[1]=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]){
            prime[++cnt]=i;
            mx[i%4]=i;
        }
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++){
            vis[i*prime[j]]=1;
            if(!(i%prime[j])){
                break;
            }
        }
        v[i]=!(i%4)?i/2:(i-mx[i%4])/2+1;
    }
}
int main(){
    scanf("%d",&T);
    get_prime(M);
    while(T--){
        scanf("%d",&n);
        ans=1e9;
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            if(v[x]/2<ans/2)
                ans=v[x];
        }
        if(ans&1){
            puts("Farmer John");
        }
        else{
            puts("Farmer Nhoj");
        }
    }
    return 0;
}
posted @ 2022-12-28 18:50  徐子洋  阅读(42)  评论(0)    收藏  举报  来源