2019 CSP-S1 提高组第1轮 三、完善程序(单选题,每题 3 分,共计 30 分)2、取石子

Alice 和 Bob 两个人在玩取石子游戏,他们制定了 n 条取石子
的规则,第 i 条规则为:
如果剩余的石子个数大于等于 a[i]且大于等于 b[i],
那么她们可以取走 b[i]个石子。他们轮流取石子。

如果轮到某个人取石子,而她们无法按照任何规则取走石子,那么他就输了,
一开始石子有 m 个。
请问先取石子的人是否有必胜的方法?

输入

第一行有两个正整数,分别为规则个数 n(1≤n≤64),以及石子个数 m(≤10^7)。
接下来 n 行。第 i 行有两个正整数 a[i]和 b[i]。(1≤a[i]≤10^7,1<=b[i]≤64)
如果先取石子的人必胜,那么输出“Win”,否则输出“Loss”

提示:
可以使用动态规划解决这个问题。由于 b[i]不超过,所以可以使用位无符号整数
去压缩必要的状态。
Status 是胜负状态的二进制压缩,trans 是状态转移的二进制压缩。
试补全程序。

代码说明:

“~”表示二进制补码运算符,它将每个二进制位的 0 变成 1、1 变为 0;
而“^”表示二进制异或运算符,它将两个参与运算的数重的每个对应的二进制位
一一进行比较,若两个二进制位相同,则运算结果的对应二进制位为 0,反之为 1。
Ull 标识符表示它前面的数字是 unsigned long long 类型。

1
31
3 5

1
19
2 6

6 6
6 1

6 6
6 1
先手必胜 

2
31
6 8
3 5


常规地,可以定义bool数组f[i],
其中f[i]表示有i个石子时,先手是否有必胜的策略,
取值1表示有必胜策略,取值0表示无必胜策略.

若对于i个石子先手有必胜策略,则存在j(i>=a[j] 且 i>=b[j])
对于i-b[j]个石子先手无必胜策略,因此得到状态转移方程

f[i]= |{ !f[i-b[j]] }( i>=a[j] 且 i>=b[j]);

f[i]= OR { !f[i-b[j]] }( a[j]<=i 且 b[j]<=i);

(1≤a[i]≤10^7,1<=b[i]≤64

f[i]=1  f[i-b[j]]=0
f[i]=0  f[i]=0 || 0

但注意到本题中b数组取值不超过64,因此在计算f[i]时仅需考虑
f[i-1,...i-64]的取值即可,故将些部分状态进行压缩

程序中,status用于记录对于i个石子,i-1...i-64是否存在必胜策略,
其二进制右起第j位的值即i-j是否有必胜策略.



/*
CSP 2019 提高组第一轮 第 20 题 (取石子) 

Alice 和 Bob 两个人在玩取石子游戏,他们制定了 n 条取石子
的规则,第 i 条规则为:
如果剩余的石子个数大于等于 a[i]且大于等于 b[i],
那么她们可以取走 b[i]个石子。他们轮流取石子。

如果轮到某个人取石子,而她们无法按照任何规则取走石子,那么他就输了,
一开始石子有 m 个。
请问先取石子的人是否有必胜的方法?

输入第一行有两个正整数,分别为规则个数 n(1≤n≤64),以及石子个数 m(≤10^7)。
接下来 n 行。第 i 行有两个正整数 a[i]和 b[i]。(1≤a[i]≤10^7,1<=b[i]≤64)
如果先取石子的人必胜,那么输出“Win”,否则输出“Loss”

提示:
可以使用动态规划解决这个问题。由于 b[i]不超过,所以可以使用位无符号整数
去压缩必要的状态。
Status 是胜负状态的二进制压缩,trans 是状态转移的二进制压缩。
试补全程序。

代码说明:

“~”表示二进制补码运算符,它将每个二进制位的 0 变成 1、1 变为 0;
而“^”表示二进制异或运算符,它将两个参与运算的数重的每个对应的二进制位
一一进行比较,若两个二进制位相同,则运算结果的对应二进制位为 0,反之为 1。
Ull 标识符表示它前面的数字是 unsigned long long 类型。

1
31
3 5

1
19
2 6

6 6
6 1

6 6
6 1
先手必胜 

2
31
6 8
3 5


常规地,可以定义bool数组f[i],
其中f[i]表示有i个石子时,先手是否有必胜的策略,
取值1表示有必胜策略,取值0表示无必胜策略.

若对于i个石子先手有必胜策略,则存在j(i>=a[j] 且 i>=b[j])
对于i-b[j]个石子先手无必胜策略,因此得到状态转移方程

f[i]= |{ !f[i-b[j]] }( i>=a[j] 且 i>=b[j]);

f[i]= OR { !f[i-b[j]] }( a[j]<=i 且 b[j]<=i);

(1≤a[i]≤10^7,1<=b[i]≤64

f[i]=1  f[i-b[j]]=0
f[i]=0  f[i]=0 || 0

但注意到本题中b数组取值不超过64,因此在计算f[i]时仅需考虑
f[i-1,...i-64]的取值即可,故将些部分状态进行压缩

程序中,status用于记录对于i个石子,i-1...i-64是否存在必胜策略,
其二进制右起第j位的值即i-j是否有必胜策略.
*/
#include <bits/stdc++.h>
#include <cstdio>
#include<algorithm>
using namespace std;
const int maxn = 64;
int n, m;
int a[maxn], b[maxn];
//U11 标识符表示它前面的数字是 unsigned long long 类型。
unsigned long long status, trans;
bool win;
int main( void )
{
    //输入第一行有两个正整数,分别为规则个数n (1<n<64),
	//以及石子个数 m(<=10^7)。
	scanf("%d%d", &n, &m);
    
    /*
    第 i 条规则为:
	如果剩余的石子个数  >= a[i]且  >=b[i],
	那么她们可以取走 b[i]个石子。
    */
	for (int i = 0; i < n; ++i)
        scanf("%d%d", &a[i], &b[i]);
    
	//从小到大排序 选择排序 
	for( int i = 0; i < n; ++i)
        for(int j = i + 1; j < n; ++j)
            if (a[i] > a[j])
			{
                swap(a[i], a[j]);
                swap(b[i], b[j]);
            }
    
    //排序后的结果 
	for (int i = 0; i < n; ++i)
        cout<<"a["<<i<<"]="<<a[i]<<" b["<<i<<"]="<<b[i]<<endl;
        
	//Status 是胜负状态的二进制压缩
	//程序中,status用于记录对于i个石子,i-1...i-64是否存在必胜策略,
    //其二进制右起第j位的值即i-j是否有必胜策略.
	status = ~0ull^1; //①1111111111111(63个1)+0(1个) 
    //按位取反运算符 status=0; 按位异或^ 
    
    //cout<<"01status="<<status<<endl;
    //printf("01status=%d\n",status);
    /*
    此处为初始化,石子数为0时,先手必输,即对于1个石子
	status二进制右起第1位为0(必输),其余为1(必胜)
    */
    
	//trans 是状态转移的二进制压缩。
	trans = 0;
    cout<<"000trans="<<trans<<endl;
    for( int i = 1, j = 0; i <= m; ++i)
	{
        while (j < n && a[j]==i /*②*/ )
        /*
        程序第16-21行对所有取石子规则按a[i]值进行了排序,
		因此动态规划时,随着石子数i的增加,合法的取石子规则只增不减.
		此处用于寻找新的合法的取石子规则因此要求a[j]=i
        */
		{
			cout<<"j="<<j<<" a["<<j<<"]="<<a[j]<<" i="<<i<<endl;
            trans|=1ull<<(b[j]-1);//③
            cout<<"j="<<j<<" a["<<j<<"]="<<a[j]<<" b["<<j<<"]="<<b[j]<<" trans="<<trans<<endl;
            /*
            trans用于记录对于当前的石子数i,有哪些取石子规则是可用的,
            其二进制右起第j位表示是否可以从i-j个石子数的状态转移过来.
            此行程序用于新增一条规则,即此时可以在原有规则基础上,新增
            "取b[j]个石子"的规则
            */
            ++j;
        }
        cout<<"~status="<<~status<<" trans="<<trans<<endl;
		win = ~status & trans; //④~按位取反运算符;&按位与
        cout<<"i="<<i<<" win="<<win<<endl;
        /*
        此处用于判断当前石子数i能否先手必胜,
		即要求存在某个前置状态无先必胜策略
        */
        status=status<<1^win;//⑤;<<左移
        cout<<"02status="<<status<<endl;
        cout<<"取反:"<<~status<<endl; 
        /*
        此处将"i个石子是否先手必胜"添加入压缩后的状态status中。
		根据上述对status的理解,应将当下状态的结果添加到status的右起第1位
        */
        cout<<"-----------------------------"<<endl;
    }

    puts(win ? "Win" : "Loss");

    return 0;
}
/*

2
31
3 5
6 8

2
31
3 5
6 9

2
31
5 3
9 6


2
31
35 3
39 6
	
①处应填( )
||| 0 ||| ~0ull ||| ~0ull ||| 1

②处应填( )
||| a[j] < i ||| a[ j ] == i ||| a[j] !=i ||| a[j]>1

③处应填( )
||| trans |=1ull << (b[j] - 1)
||| status |=1ull << (b[j] - 1)
||| status +=1ull << (b[j] - 1)
||| trans +=1ull << (b[j] - 1)

④处应填( )
||| ~status | trans ||| status & trans
||| status | trans ||| ~status & trans

⑤处应填( )
||| trans = status | trans ^ win
||| status = trans >> 1 ^ win
||| trans = status ^ trans | win
||| status = status << 1 ^ win

1.
 A. 0
 B. ~0ull
 C. ~0ull
 D. 1
2.
 A. a[j] < i
 B. a[ j ] == i
 C. a[j] !=i
 D. a[j]>1
3.
 A. trans |=1ull << (b[j] - 1)
 B. status |=1ull << (b[j] - 1)
 C. status +=1ull << (b[j] - 1)
 D. trans +=1ull << (b[j] - 1)
4.
 A. ~status| trans
 B. status & trans
 C. status | trans
 D. ~status & trans
5.
 A. trans =status | trans ^ win
 B. status = trans >> 1 ^ win
 C. trans =status ^ trans | win
 D. status = status << 1 ^ win

本题共 15 分

解析:首先使用 f(i)表示有 i 个石子时,是否有必胜策略。
所以 f(i)=!f(i-b[j1]) or !f(i-b[j2]) ...) (a[j]<=i), 
转换公式,status 中每一位定义为 win(i-j),
也就是有 i-j 有必胜策略。

因此第一题初始状态为都必输,因为石子有 0 个,怎么样都是输的。

然后trans相当于记录当前状态下能够必胜的策略位置也就是b[j]的集合,

但是因为要注意这边 trans 没有清 0,

因为我们考虑到事实上能转移的状态数是不会减少的,

所以这边第二题选 B,表示将当前的状态增加到 trans 里面,

同时第三题选择 A 表示的就是将 b[j]加到 trans 里面
(记录新增的能够必胜的位置),

然后第 4 题相当于往 status 记录新的必胜策略的位置(也就是 trans), 

所以按照上述的转移公式 f(), 第 4 题答案也就是 D 了, 

因为先手必胜的情况等价于,当前状态下能走到的先手必输的情况不为空。

最好将 status 状态更新,

具体就是将当前的 win 记录到 status 的最低位上即可(第 5 题)
*/



NOIP 提高组 初赛 三、问题求解 习题集NOIP1995-NOIP2018

NOIP 提高组 初赛 三、问题求解 习题集NOIP1995-NOIP2018_noi提高组初赛题-CSDN博客

2019 CSP-J1 CSP-S1 第1轮 初赛 答案解析及总结、视频等

2019 CSP-J1 CSP-S1 第1轮 初赛 答案解析及总结、视频等_dllglvzhenfeng的博客-CSDN博客

2020 CSP-J1 CSP-S1 第1轮 初赛 答案解析及总结、视频等

2020 CSP-J1 CSP-S1 第1轮 初赛 答案解析及总结、视频等_dllglvzhenfeng的博客-CSDN博客

2021 CSP-J1 CSP-S1 第一轮 初赛 相关题解及视频等

2021 CSP-J1 CSP-S1 第一轮 初赛 相关题解及视频等_dllglvzhenfeng的博客-CSDN博客

2019-2021 CSP-J1 CSP-S1 第一轮 初赛 相关题解及视频等

2019-2021 CSP-J1 CSP-S1 第一轮 初赛 相关题解及视频等_csp-j/s课程_dllglvzhenfeng的博客-CSDN博客

NOIP初赛 CSP-J1 CSP-S1 第1轮 初赛 信奥中的数学知识(一)

NOIP初赛 CSP-J1 CSP-S1 第1轮 初赛 信奥中的数学知识(一)-CSDN博客

NOIP初赛 CSP-J1 CSP-S1 第1轮 初赛 信奥中的数学知识(二)

NOIP初赛 CSP-J1 CSP-S1 第1轮 初赛 信奥中的数学知识(二)_dllglvzhenfeng的博客-CSDN博客








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

dllglvzhenfeng

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

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

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

打赏作者

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

抵扣说明:

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

余额充值