这周弄得是博弈,基础的算是看懂了,但是稍微难一点或者是加上dp就又懵逼了,再加上hdu的关闭能做的题只有洛谷,能刷的题目也变少了,但还是先把洛谷上的题目给刷完吧。。。
p7589
淦,这题竟是一个nim游戏的板子,对一条直线,一方棋子到另一方所需要走的步数就可以看做一堆石子的数量,而一共有n条直线,就是n堆石子,而后退这一个功能就好比把拿走的石子在放回去,那么下一步再拿走不就完了,所以没有影响,所以这是一道nim游戏
题解 P7589 【黑白棋(2021 CoE-II B)】 - VinstaG173 - 洛谷博客
#include<bits/stdc++.h>
using namespace std;
int main(){
// freopen("in.txt","r",stdin);
int t;cin>>t;
while(t--){
int n,k,d,sum=0;
int x,a,b;
cin>>n>>k>>d;
for(int i=1;i<=n;i++){
cin>>x>>a>>b;
sum^=a-b>=0?(a-b-1):(b-a-1);
}
if(sum==0) cout<<"No"<<endl;
else cout<<"Yes"<<endl;
}
return 0;
}
p2575高手过招
一道阶梯nim;
阶梯nim:有n层台阶,每个台阶上方有一些石子,A和B轮流操作,将石子放到下1层,不能操作的人输,即石子全都放到地上(第0层)。
先手必胜的方法:对每个奇数层的石子数的sg值异或,如果不为零则先手必胜,为零就必败;
简要说明为何是奇数层:第0层是必败的层,0也为偶数层,若先手把偶数层上的石子放到了下一层,也就是奇数层,那么后手就可以再把刚才被移动的那些石子移动到下一层,这样就一直循环到把这些石子放到地上,然后又轮到先手;所以偶数层的石子就相当于没有,因为总是可以用有限步数把它们放到地面上而不破坏先手和后手的顺序。
回到这道题,有棋子为1,无为0,首先根据题意,地面应该是最后连续为1的部分,第一个阶梯(阶梯或台阶都行,说法而已)为从右往左数第一个空格一直到下一个空格这个区间内棋子的个数,这样就可以转化为阶梯nim了,最后一段可能从左往右数最后一个格子不是空格,但也算一段,不要忘了异或;另外看的题解感觉这个判断奇偶的方法也不错:fg一开始为零,遇到第一个空格后取反,表明这层是奇数层,再遇到空格又取反,表明下一层是偶数层记录的石子数无效,再下一层又是奇数层,一直重复到循环结束
思路主要是看的这篇博客才懂的
题解 P2575 【高手过招】 - VinstaG173 - 洛谷博客
代码看的这篇
Luogu P2575 高手过招_weixin_34278190的博客-CSDN博客
#include<bits/stdc++.h>
using namespace std;
int main(){
// freopen("in.txt","r",stdin);
int t;cin>>t;
int vis[22];
while(t--){
int ans=0;
int n;cin>>n;
while(n--){
int m;cin>>m;
int x,fg=0,tot=0,p=20;
//ans计算异或值,fg判断是否是奇偶,tot统计棋子个数
memset(vis,0,sizeof(vis));
for(int i=1;i<=m;i++){
cin>>x;vis[x]=1;
}
while(vis[p]) p--;
while(p>=1){
if(!vis[p]){
ans^=fg?tot:0;//奇数就参与异或
fg=!fg;
tot=0;
}
else tot++;
p--;
}
ans^=fg?tot:0;
}
cout<<(ans?"YES":"NO")<<endl;
}
return 0;
}
p2964
每个人的题解都不同,甚至有些时候竟然是相反的观点或思路,导致我看了一篇又一篇却怎么都看不懂该去如何理解如何做这道题。。。
最后我个人比较接受的理解是dp[i][j]代表从i开始拿,上一个人已经拿了j个,然后你再拿k个(k=1--2*j),那么你之后的玩家所能拿到的石子总数就是剩下的总石子数sum[i]减去你拿了dp[i-k][k]后的最大值,如此往复,每个人都是最优策略;姑且可以这样理解吧。。。对dp理解的程度还是远远不够,总觉得没有触碰到dp最本质的地方——状态转移,还是没有搞懂这个,呼,难搞。。。
#include<bits/stdc++.h>
using namespace std;
int n,a[2005],sum[2005],dp[2005][2005];
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=n;i>=1;i--)scanf("%d",&a[i]);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
int k=2*j-1;
dp[i][j]=dp[i][j-1];
if(k<=i) dp[i][j]=max(dp[i][j],sum[i]-dp[i-k][k]);
if(k+1<=i) dp[i][j]=max(dp[i][j],sum[i]-dp[i-k-1][k+1]);
}
printf("%d\n",dp[n][1]);
return 0;
}
p4101
最优的方法一定是要把石子都合成一堆m个,最后必会剩下n/m堆m个的石子,如果m不整除n的话,还有余数的一堆;这样就可以算出一共合并了多少次(每合并一次总的石子堆就减一,可以根据这个算出合并次数),如果是奇数次就是0,偶数次就是1;
题解 P4101 【[HEOI2014]人人尽说江南好 】 - 竹窝呵呵 的博客 - 洛谷博客
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,t,m;cin>>t;
while(t--){
cin>>n>>m;
int c=n-(n/m+(n%m!=0));
if(c&1) cout<<0<<endl;
else cout<<1<<endl;
}
return 0;
}
p5675
首先要确定怎样才是必输的局面,我们可以先看看必胜的局面:挑一堆石子,如果这一堆石子的个数大于其它总的石子个数的异或和的话那他必然可以让他的下一个人必败,所以必输的条件就是挑一堆石子个数小于等于其它石子个数的异或和;dp[i][j]表示前i堆除了选中的那一堆,异或和为j的方案数,dp求出所有异或的方案数,然后把大于等于a[i]的加起来就是答案
题解 P5675 【[GZOI2017]取石子游戏】 - Resuscitate - 洛谷博客
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
ll n,dp[205][260],a[205];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
ll ans=0;dp[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
for(int k=0;k<256;k++){
if(i==j) dp[j][k]=dp[j-1][k];
else dp[j][k]=dp[j-1][k]+dp[j-1][k^a[j]];
dp[j][k]%=mod;
}
for(int j=a[i];j<256;j++) ans=(ans+dp[n][j])%mod;
}
cout<<ans<<endl;
return 0;
}