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 51
19
2 66 6
6 16 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
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 第一轮 初赛 相关题解及视频等
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轮 初赛 信奥中的数学知识(二)
NOIP初赛 CSP-J1 CSP-S1 第1轮 初赛 信奥中的数学知识(二)_dllglvzhenfeng的博客-CSDN博客