Codeforces Round #406 (Div. 2) C 博弈

题目链接

此题类似于当初上ACM公选课博弈一节的某例题,考察了对于必胜点,必败点等基础知识的掌握。

对于此题:

必败点(P点) :前一个选手(Previous player)将取胜的位置称为必败点。
必胜点(N点) :下一个选手(Next player)将取胜的位置称为必胜点。

对于必败点和必胜点,分别有以下性质:

必败点:无论怎么操作,从必败点下一步都只能进入必胜点。
必胜点:至少存在一种操作,能从必胜点走到必败点。

上述概念看似有点抽象,对于本题,简单理解就是:
当轮到某玩家操作时,无论怎么操作,操作结束后都是对方胜利,此时就是必败点。考虑特殊例子的话,当棋子已经走到了 1 的点,游戏已经结束,这时对于下一个操作的人此时就是必败点。
而如果轮到某玩家操作时,他有多种不同的操作选择,但存在一种操作使得他选择后,对方无论怎么操作都是输,即使对方进入必败点,则该玩家此时处于必胜点。

所以对于本题,我们可以考虑在时间轴上从后往前BFS,因为游戏结束有着明确的规定,即棋子移动到了 1 的点。若棋子已经在 1 的点,则谁先手谁就已经输了(因为在先手操作前棋子已经到达1,则说明是后手达到胜利条件)。

以 1 作为必败点,从后往前搜索,则存在两种情况:
(1).当前状态为必败点。
则上一个状态必为必胜点。(如果某一个状态通过某一个操作能走到必败点,则该点一定是必胜点)
(2).当前状态为必胜点。
此时情况便复杂一点,因为此时有两种可能:

必胜点 --> 必胜点
必败点 --> 必胜点

故我们可以用一个数组保存某个点到达必胜点的次数,若能够达到必胜点的次数等于总的操作数,即无论怎么操作,都只能从当前状态到达必胜点。
则当前状态为必败点。

则便是我下面代码里面的出度数组: deg[]
因为总的操作数是 k1 / k2,故我们可以提前预处理deg数组为总的操作数,一旦能够到达某一个必胜点,则出度减一,当出度为0时,则达到了上述条件。

然后没有更新答案的点就是可以为圈的点。
代码:

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;

const int INF = 1e9 + 7;
const int A = 1e4 + 100;
class P{
public:
    int pos,k;
    P(int i,int j):pos(i),k(j){}
};
int val_1[A],val_2[A];
int deg_1[A],deg_2[A];
int ans_1[A],ans_2[A];  // 1 必败 2 必胜
queue<P> que;


int main(){
    int n;
    scanf("%d",&n);

    int k1,k2;
    scanf("%d",&k1);
    for(int i=1 ;i<=k1 ;i++) scanf("%d",&val_1[i]);
    scanf("%d",&k2);
    for(int i=1 ;i<=k2 ;i++) scanf("%d",&val_2[i]);

    ans_1[1] = ans_2[1] = 1;
    que.push(P(1,1)),que.push(P(1,2));
    for(int i=1 ;i<=n ;i++){
        deg_1[i] = k1;
        deg_2[i] = k2;
    }
    deg_1[1] = deg_2[1] = INF;

    while(que.size()){
        P now = que.front();que.pop();

        if(now.k == 1){
            for(int i=1 ;i<=k2 ;i++){
                int ne = (now.pos - val_2[i] - 1 + n) % n + 1;   //避免产生0
                if(ans_2[ne]) continue;

                if(ans_1[now.pos] == 1){
                    ans_2[ne] = 2;
                    que.push(P(ne,2));
                }
                else{
                    deg_2[ne]--;
                    if(deg_2[ne] == 0){
                        ans_2[ne] = 1;
                        que.push(P(ne,2));
                    }

                }
            }
        }
        else{
            for(int i=1 ;i<=k1 ;i++){
                int ne = (now.pos - val_1[i] - 1 + n) % n + 1;
                if(ans_1[ne]) continue;

                if(ans_2[now.pos] == 1){
                    ans_1[ne] = 2;
                    que.push(P(ne,1));
                }
                else{
                    deg_1[ne]--;
                    if(deg_1[ne] == 0){
                        ans_1[ne] = 1;
                        que.push(P(ne,1));
                    }
                }
            }
        }
    }

    for(int i=2 ;i<=n ;i++){
        if(ans_1[i] == 2) printf("Win ");
        else if(ans_1[i] == 1) printf("Lose ");
        else    printf("Loop ");
    }
    puts("");
    for(int i=2 ;i<=n ;i++){
        if(ans_2[i] == 2) printf("Win ");
        else if(ans_2[i] == 1) printf("Lose ");
        else    printf("Loop ");
    }
    return 0;
}
技术选型 【后端】:Java 【框架】:springboot 【前端】:vue 【JDK版本】:JDK1.8 【服务器】:tomcat7+ 【数据库】:mysql 5.7+ 项目包含前后台完整源码。 项目都经过严格调试,确保可以运行! 具体项目介绍可查看博主文章或私聊获取 助力学习实践,提升编程技能,快来获取这份宝贵的资源吧! 在当今快速发展的信息技术领域,技术选型是决定一个项目成功与否的重要因素之一。基于以下的技术栈,我们为您带来了一份完善且经过实践验证的项目资源,让您在学习和提升编程技能的道路上事半功倍。以下是该项目的技术选型和其组件的详细介绍。 在后端技术方面,我们选择了Java作为编程语言。Java以其稳健性、跨平台性和丰富的库支持,在企业级应用中处于领导地位。项目采用了流行的Spring Boot框架,这个框架以简化Java企业级开发而闻名。Spring Boot提供了简洁的配置方式、内置的嵌入式服务器支持以及强大的生态系统,使开发者能够更高效地构建和部署应用。 前端技术方面,我们使用了Vue.js,这是一个用于构建用户界面的渐进式JavaScript框架。Vue以其易上手、灵活和性能出色而受到开发者的青睐,它的组件化开发思想也有助于提高代码的复用性和可维护性。 项目的编译和运行环境选择了JDK 1.8。尽管Java已经推出了更新的版本,但JDK 1.8依旧是一种成熟且稳定的选择,广泛应用于各类项目中,确保了兼容性和稳定性。 在服务器方面,本项目部署在Tomcat 7+之上。Tomcat是Apache软件基金会下的一个开源Servlet容器,也是应用最为广泛的Java Web服务器之一。其稳定性和可靠的性能表现为Java Web应用提供了坚实的支持。 数据库方面,我们采用了MySQL 5.7+。MySQL是一种高效、可靠且使用广泛的关系型数据库管理系统,5.7版本在性能和功能上都有显著的提升。 值得一提的是,该项目包含了前后台的完整源码,并经过严格调试,确保可以顺利运行。通过项目的学习和实践,您将能更好地掌握从后端到前端的完整开发流程,提升自己的编程技能。欢迎参考博主的详细文章或私信获取更多信息,利用这一宝贵资源来推进您的技术成长之路!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值