牛牛最近迷上了一种叫斗地主的扑克游戏。斗地主是一种使用黑桃、红心、梅花、方片的A到K加上大小王的共54张牌来进行的扑克牌游戏。在斗地主中,牌的大小关系根据牌的数码表示如下:3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A < 2 < 小王 < 大王,而花色并不对牌的大小产生影响。每一局游戏中,一副手牌由n张牌组成。游戏者每次可以根据规定的牌型进行出牌,首先打光自己的手牌一方取得游戏的胜利。
现在,牛牛只想知道,对于自己的若干组手牌,分别最少需要多少次出牌可以将它们打光。请你帮他解决这个问题。
需要注意的是,本题中游戏者每次可以出手的牌型与一般的斗地主相似而略有不同。
具体规则如下:
第一行包含用空格隔开的2个正整数T和n,表示手牌的组数以及每组手牌的张数。
接下来T组数据,每组数据n行,每行一个非负整数对aibi表示一张牌,其中ai示牌的数码,bi表示牌的花色,中间用空格隔开。特别的,我们用1来表示数码A,11表示数码J,12表示数码Q,13表示数码K;黑桃、红心、梅花、方片分别用1-4来表示;小王的表示方法为01,大王的表示方法为02。
共T行,每行一个整数,表示打光第i手牌的最少次数。
1 8
7 4
8 4
9 1
10 4
11 1
5 1
1 4
1 1
3
1 17
12 3
4 3
2 3
5 4
10 2
3 3
12 2
0 1
1 3
10 1
6 2
12 1
11 3
5 2
12 4
2 2
7 2
6
样例1说明
共有1组手牌,包含8张牌:方片7,方片8,黑桃9,方片10,黑桃J,黑桃5,方片A以及黑桃A。可以通过打单顺子(方片7,方片8,黑桃9,方片10,黑桃J),单张牌(黑桃5)以及对子牌(黑桃A以及方片A)在3次内打光。
对于不同的测试点, 我们约定手牌组数T与张数n的规模如下:
数据保证:所有的手牌都是随机生成的。
这道题本来是只拿30分的,然后看了别人的题解才改出来的
30分还是很好写的,对几种情况判断一下就好了
至于想拿满分,首先要明确一点就是,这个题目中,花色无用,大小无用
而且这道题唯一对最后的答案有影响的就是顺子,除此之外最后的结果都是固定的
那我们就可以用一个DP数组来存固定牌组(i个炸弹,j个三张,k个对子,l张单牌)时出完所有牌的步数
然后深搜处理一下顺子的情况
剩下的直接看代码吧
#include
#include
#include
using namespace std;
int dp[25][25][25][25],card[15],h[15];
//dp[i][j][k][l]表示i组四张,j组三张,k组两张,l组单牌所需要的最少步数
int n,t,color,point,ans;
int tnum[5]={0,5,3,2};
inline int read()
{
int sum=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch<='9'&&ch>='0')
{
sum=sum*10+ch-48;
ch=getchar();
}
return sum;
}
int dk(int o,int t2,int t3,int f,int k) //处理joker
{
if(k==1) //只有一张joker的时候
{
o++; //就把joker视为一张单牌
k=0;
}
if(k==0) return dp[f][t3][t2][o];
else return min(dp[f][t3][t2][o+2],dp[f][t3][t2][o]+1);
}
void dfs(int t) //t记录的是 目前为止的次数
{
if(t>ans) return; //边界条件,当当前次数大于最差情况直接return
memset(h,0,sizeof(h));
for(int i=2;i<=14;i++) h[card[i]]++;
ans=min(ans,t+dk(h[1],h[2],h[3],h[4],card[0])); //先把带有joker的情况变成常规情况
for(int k=1;k<=3;k++) //把顺子先处理掉
for(int i=3;i<=14;i++)
{
int j;
for(j=i;j<=14&&card[j]>=k;j++)
{
card[j]-=k;
if(j-i+1>=tnum[k]) dfs(t+1);
}
for(j--;j>=i;j--) card[j]+=k;
}
}
void pre()
{
memset(dp,1,sizeof(dp));
dp[0][0][0][0]=0;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
for(int k=0;k<=n;k++)
for(int l=0;l<=n;l++)
if(i*4+j*3+k*2+l<=n) //确保是合法的情况
{
dp[i][j][k][l]=i+j+k+l; //这是最差的情况
if(i)
{
if(k>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k-2][l]+1);// 四带一对
if(l>=2) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l-2]+1);// 一对单牌
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i-1][j][k][l]+1); //啥都不带
}
if(j)
{
if(k) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k-1][l]+1); // 3带一对
if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l-1]+1); // 3带单
dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j-1][k][l]+1); // 什么都不带
}
if(k) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k-1][l]+1); //对子
if(l) dp[i][j][k][l]=min(dp[i][j][k][l],dp[i][j][k][l-1]+1); //单牌
}
}
int main()
{
t=read();n=read();
pre();//预处理
while(t--)
{
memset(card,0,sizeof(card)); //每次记得清零
ans=n; //先设置最坏的情况
for(int i=1;i<=n;i++)
{
point=read(),color=read();
if(point==1) card[14]++;
//因为2不能算顺子,所以将A变为14方便发顺子
else card[point]++;
}
dfs(0); //遍历
printf("%d\n",ans);
}
return 0;
}
诈尸,这道题竟然还有 数据加强版 其实也只是Dp数组追加几个条件而已
自己看代码吧
#include
#include
#include
using namespace std;
int dp[25][25][25][25],card[15],h[15];
//dp[i][j][k][l]表示i组四张,j组三张,k组两张,l组单牌所需要的最少步数
int n,t,color,point,ans;
int tnum[5]={0,5,3,2};
inline int read()
{
int sum=0;
char ch=getchar();
while(ch>'9'||ch<'0') ch=getchar();
while(ch<='9'&&ch>='0')
{
sum=sum*10+ch-48;
ch=getchar();
}
return sum;
}
int dk(int o,int t2,int t3,int f,int k) //处理joker
{
if(k==1) //只有一张joker的时候
{
o++; //就把joker视为一张单牌
k=0;
}
if(k==0) return dp[f][t3][t2][o];
else return min(dp[f][t3][t2][o+2],dp[f][t3][t2][o]+1);
}
void dfs(int t) //t记录的是 目前为止的次数
{
if(t>ans) return; //边界条件,当当前次数大于最差情况直接return
memset(h,0,sizeof(h));
for(int i=2;i<=14;i++) h[card[i]]++;
ans=min(ans,t+dk(h[1],h[2],h[3],h[4],card[0])); //先把带有joker的情况变成常规情况
for(int k=1;k<=3;k++) //把顺子先处理掉
for(int i=3;i<=14;i++)
{
int j;
for(j=i;j<=14&&card[j]>=k;j++)
{
card[j]-=k;
if(j-i+1>=tnum[k]) dfs(t+1);
}
for(j--;j>=i;j--) card[j]+=k;
}
}
void pre()
{
dp[0][0][0][0]=0;
for(int i=0;i<=n;i++)//四
for(int j=0;j<=n;j++)//三
for(int k=0;k<=n;k++)//二
for(int l=0;l<=n;l++)//一
if(i*4+j*3+k*2+l<=n)
{
dp[i][j][k][l]=i+k+j+l;//手牌最差
if(i)
{
if(i>=1) dp[i][j][k][l]=min(dp[i-1][j+1][k][l+1],dp[i][j][k][l]);//四拆成三张和一张单牌
if(k>=2) dp[i][j][k][l]=min(dp[i-1][j][k-2][l]+1,dp[i][j][k][l]);//四带两对
if(i>=2) dp[i][j][k][l]=min(dp[i-2][j][k][l]+1,dp[i][j][k][l]); //两炸拆成四带两对
if(k>=1) dp[i][j][k][l]=min(dp[i-1][j][k-1][l]+1,dp[i][j][k][l]);//四带两张成对的单
if(l>=2) dp[i][j][k][l]=min(dp[i-1][j][k][l-2]+1,dp[i][j][k][l]);//四带二
dp[i][j][k][l]=min(dp[i-1][j][k][l]+1,dp[i][j][k][l]);
}
if(j)
{
if(j>=1) dp[i][j][k][l]=min(dp[i][j-1][k+1][l+1],dp[i][j][k][l]); //三拆成一对和一单
if(k>=1) dp[i][j][k][l]=min(dp[i][j-1][k-1][l]+1,dp[i][j][k][l]); //三带一对
if(l>=1) dp[i][j][k][l]=min(dp[i][j-1][k][l-1]+1,dp[i][j][k][l]); //三带一
dp[i][j][k][l]=min(dp[i][j-1][k][l]+1,dp[i][j][k][l]);
}
if(k) dp[i][j][k][l]=min(dp[i][j][k-1][l]+1,dp[i][j][k][l]);//对
if(l) dp[i][j][k][l]=min(dp[i][j][k][l-1]+1,dp[i][j][k][l]);//单
}
}
int main()
{
t=read();n=read();
pre();//预处理
while(t--)
{
memset(card,0,sizeof(card)); //每次记得清零
ans=n; //先设置最坏的情况
for(int i=1;i<=n;i++)
{
point=read(),color=read();
if(point==1) card[14]++;
//因为2不能算顺子,所以将A变为14方便发顺子
else card[point]++;
}
dfs(0); //遍历
printf("%d\n",ans);
}
return 0;
}