[USACO17JAN] Hoof, Paper, Scissor G (dfs记忆化)
题意
三个手势'H','S','P'
分别对应蹄子(石头)剪刀布,规则应该都清楚,注意平局也算不赢,不计分
已知对方后n局出什么
自己可以选择第一次出什么手势(不计入更改次数)
且手势可以更改k次,问n局最大分数
思路
跑三种手势开头的dfs
,取三种最大分数
代码&艰辛的卡题过程
第一版代码是暴力,具体看代码注释
注意如果要转,手势就不能和上次手势一样,不然就变成了没转手势但是次数+1的迷惑事件
Code:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k;
int maxx=0;
char f[3]={'H','S','P'};//石头,剪刀,布
char a[maxn];//记录对手的手势
bool pd(char x,char y)//x,y两个人的手势,返回x这个人这次赢了吗
{
if(x==y) return 0;//一样的情况
if(x=='H')//石头
{
if(y=='S') return 1;//对剪刀
else return 0;//对 布
}
if(x=='S')//剪刀
{
if(y=='P') return 1;//对 布
else return 0;//对石头
}
if(x=='P')//布
{
if(y=='H') return 1;//对石头
else return 0;//对剪刀
}
}
void dfs(int now,int x,int cnt,int sum)//此时的手势,进行到第几轮了,目前转了多少次,赢了几次
{
// cout<<f[now]<<" "<<x<<" "<<cnt<<" "<<sum<<endl;
if(x>n)
{
maxx=max(sum,maxx);//更新最大值
return;
}
//分为:不转和转
//1.不转:
dfs(now,x+1,cnt,sum+pd(f[now],a[x]));
for(int i=0;i<3;i++)
{
//2.次数可以转就转
if(cnt<k&&i!=now)dfs(i,x+1,cnt+1,sum+pd(f[i],a[x]));
}
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
dfs(0,1,0,0);
dfs(1,1,0,0);
dfs(2,1,0,0);
cout<<maxx;
return 0;
}
这份代码得到了20tps,大数据(甚至算不上大)全T了,考虑优化
剪枝是不可能剪了,考虑记忆化
由于(我认为) void
类型的函数实在是不好用记忆化,在和hqh讨论后将 dfs
改成int
类型,修改亿点点
Code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k;
char f[3]={'H','S','P'};//石头,剪刀,布
char a[maxn];//记录对手的手势
int vis[3][maxn][21];//记忆化标记数组
inline int pd(char x,char y)//x,y两个人的手势,返回x这个人这次赢了吗(其实也同样是分数,可以直接加)
{
if(x==y) return 0;//一样的情况
if(x=='H')//石头
{
if(y=='S') return 1;//对剪刀
else return 0;//对 布
}
if(x=='S')//剪刀
{
if(y=='P') return 1;//对 布
else return 0;//对石头
}
if(x=='P')//布
{
if(y=='H') return 1;//对石头
else return 0;//对剪刀
}
}
int dfs(int now,int x,int cnt)//此时的手势,进行到第几轮了,目前转了多少次,返回赢了几次
{
if(x>n)
{
return 0;//结束要返回
}
// cout<<"在第"<<x<<"个回合出"<<f[now]<<endl;
if(vis[now][x][cnt]!=0)//记忆化
{
// cout<<"此时已有在第"<<x<<"个回合出"<<f[now]<<"更改"<<cnt<<"次这一组数"<<endl;
return vis[now][x][cnt];
}
//分为:不转和转
//1.不转:
vis[now][x][cnt]=dfs(now,x+1,cnt)+pd(f[now],a[x]);
//2.次数可以转就转
int t1=-0x3f3f3f,t2=-0x3f3f3f,t3=-0x3f3f3f;
if(now!=0) t1=dfs(0,x+1,cnt+1)+pd(f[0],a[x]);//注意要+现在的分
if(now!=1) t2=dfs(1,x+1,cnt+1)+pd(f[1],a[x]);//注意要+现在的分
if(now!=2) t3=dfs(2,x+1,cnt+1)+pd(f[2],a[x]);//注意要+现在的分
vis[now][x][cnt]=max(t1,max(t2,t3));//三个取最大
// cout<<t1<<" "<<t2<<" "<<t3<<endl;
return vis[now][x][cnt];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int maxx=max(dfs(0,1,0),max(dfs(1,1,0),dfs(2,1,0)));
// cout<<dfs(0,1,0)<<endl<<dfs(1,1,0)<<endl<<dfs(2,1,0);
cout<<maxx;
return 0;
}
这份代码得到了10tps,不过不是T是wa
了
出现了两个弱智错误,竟然还卡了我30min
:
1.上一份代码在赋最大值的时候我竟然没有考虑原本赋值过的没转手势的情况,修改后变成这样:
vis[now][x][cnt]=dfs(now,x+1,cnt)+pd(f[now],a[x]);
vis[now][x][cnt]=max(max(vis[now][x][cnt],t1),max(t2,t3));//取最大
2.在有一版我的错误代码中,结束条件是if(x>n||cnt>k)
,在删除掉后者(cnt>k
)之后,就没有限制cnt
也就是转换手势次数的语句了,导致可以无限次转换手势
所以转换手势的那三个语句要多一个判断,如下:
if(now!=0&&cnt<k) t1=dfs(0,x+1,cnt+1)+pd(f[0],a[x]);
if(now!=1&&cnt<k) t2=dfs(1,x+1,cnt+1)+pd(f[1],a[x]);
if(now!=2&&cnt<k) t3=dfs(2,x+1,cnt+1)+pd(f[2],a[x]);
改完就可以AC了(丑陋的马蜂)
Code
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k;
char f[3]={'H','S','P'};//石头,剪刀,布
char a[maxn];//记录对手的手势
int vis[3][maxn][21];//记忆化标记数组
inline int pd(char x,char y)//x,y两个人的手势,返回x这个人这次赢了吗
{
if(x==y) return 0;//一样的情况
if(x=='H')//石头
{
if(y=='S') return 1;//对剪刀
else return 0;//对 布
}
if(x=='S')//剪刀
{
if(y=='P') return 1;//对 布
else return 0;//对石头
}
if(x=='P')//布
{
if(y=='H') return 1;//对石头
else return 0;//对剪刀
}
return 0;
}
int dfs(int now,int x,int cnt)//此时的手势,进行到第几轮了,目前转了多少次,返回赢了几次
{
if(x>n)
{
return 0;//结束要返回
}
// cout<<"在第"<<x<<"个回合出"<<f[now]<<"此时更改"<<cnt<<"次"<<endl;
if(vis[now][x][cnt]!=0)//记忆化
{
// cout<<"此时已有在第"<<x<<"个回合出"<<f[now]<<"更改"<<cnt<<"次这一组数"<<endl;
return vis[now][x][cnt];
}
//分为:不转和转
//1.不转:
vis[now][x][cnt]=dfs(now,x+1,cnt)+pd(f[now],a[x]);
//2.次数可以转就转
int t1=0,t2=0,t3=0;
if(now!=0&&cnt<k) t1=dfs(0,x+1,cnt+1)+pd(f[0],a[x]);
if(now!=1&&cnt<k) t2=dfs(1,x+1,cnt+1)+pd(f[1],a[x]);
if(now!=2&&cnt<k) t3=dfs(2,x+1,cnt+1)+pd(f[2],a[x]);
vis[now][x][cnt]=max(max(vis[now][x][cnt],t1),max(t2,t3));//取最大
// cout<<t1<<" "<<t2<<" "<<t3<<endl;
return vis[now][x][cnt];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int maxx=max(dfs(0,1,0),max(dfs(1,1,0),dfs(2,1,0)));
// cout<<dfs(0,1,0)<<endl<<dfs(1,1,0)<<endl<<dfs(2,1,0);
cout<<maxx;
return 0;
}
改良马蜂
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n,k;
int vis[3][maxn][21];
char f[3]={'H','S','P'},a[maxn];
int pd(char x,char y)
{
if(x==y) return 0;
if(x=='H')
{
if(y=='S') return 1;
else return 0;
}
if(x=='S')
{
if(y=='P') return 1;
else return 0;
}
if(x=='P')
{
if(y=='H') return 1;
else return 0;
}
return 0;
}
int dfs(int now,int x,int cnt)
{
if(x>n) return 0;
if(vis[now][x][cnt]!=0) return vis[now][x][cnt];
vis[now][x][cnt]=dfs(now,x+1,cnt)+pd(f[now],a[x]);
int t1=0,t2=0,t3=0;
if(now!=0&&cnt<k) t1=dfs(0,x+1,cnt+1)+pd(f[0],a[x]);
if(now!=1&&cnt<k) t2=dfs(1,x+1,cnt+1)+pd(f[1],a[x]);
if(now!=2&&cnt<k) t3=dfs(2,x+1,cnt+1)+pd(f[2],a[x]);
vis[now][x][cnt]=max(max(vis[now][x][cnt],t1),max(t2,t3));
return vis[now][x][cnt];
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
int maxx=max(dfs(0,1,0),max(dfs(1,1,0),dfs(2,1,0)));
cout<<maxx;
return 0;
}