C语言程序设计100例之(78):扑克游戏
例78 扑克游戏
问题描述
亚当和夏娃用一副52张的普通牌玩纸牌游戏。规则很简单,两人面对面坐在桌子的两侧。每人从牌堆中取出k张牌,看了之后,将牌面朝下放在桌子上。亚当的牌从左边的1到k编号,夏娃的牌从右边的1到k编号(所以夏娃的第i张牌与亚当的第i张牌相对)。卡片正面朝上,积分如下:
如果亚当的第i(i∈ {1,…,k})张牌胜过夏娃的第i张牌,那么亚当得1分。
如果夏娃的第i张牌胜过亚当的第i张牌,那么夏娃得一分。
点数较高的牌总是胜过点数较低的牌:3胜2、4胜3、…。1张A牌胜过每1张牌,除了(可能)另1张A牌。
如果两张牌的点数相同,那么牌的花色就决定了谁赢:红桃胜过所有其他牌,黑桃胜过除红桃以外的所有花色,方块只胜过梅花,梅花不胜过任何一种花色。
例如,红桃10胜过黑桃10,黑桃10胜过方块10,方块10胜过梅花10。
这应该是一场机会游戏,但最近夏娃大部分时间都赢了,原因是她已经开始使用有标记的牌。换言之,她在亚当把牌翻过来之前就知道他桌上有哪些牌。利用这些信息,她可以先排好自己的牌,以便获得尽可能多的积分。
你的任务是,根据亚当和夏娃的牌,确定夏娃如果打得最好会得到多少分。
输入
输入包括多组测试用例。第一行输入将包含一个正整数N,给出测试用例的数量。
每个测试用例从一行开始,该行带有一个正整数k<=26,这是每个玩家获得的牌数。
下一行描述了亚当放在桌子上的k张牌,从左到右。
再下一行描述了夏娃的k张牌(但她还没有把它们放在桌子上)。卡片由两个字符描述,第一个是它的点数(2、3、4、5、6、7、8、9、T、J、Q、K或A),第二个是它的花色(C、D、S或H,分别表示梅花、方块、黑桃或红桃)。牌之间用空格隔开。因此,如果亚当的牌是梅花10,红桃2和方块J,那可以描述为 TC 2H JD。
输出
对于每个测试用例,如果夏娃选择了最佳的方式将卡片排列在桌子上,输出其最多得分。
输入样例
3
1
JD
JH
2
5D TC
4C 5H
3
2H 3H 4H
2D 3D 4D
输出样例
1
1
2
(1)编程思路。
由于需要对扑克牌进行排序,而采用牌面字符串不方便进行排序,因此可以编写函数int getVal(char card[5])将52张扑克牌中的某张由card字符串指定的牌面转换为对应的整数值(8~59之一)。转换时,牌面2C=8、2D=9、2S=10、2H=11、…、AC=56、AD=57、AS=58、AH=59。
扑克牌的一张牌面包含点数和花色两个信息,可以将点数2~9分别取2~9,T、J、Q、K分别取10、11、12、13,A取14,四种花色C、D、S、H分别取0、1、2、3,这样每张牌按牌面可映射为一个整数值,映射公式为: 牌面值=点数*4+花色。
这样,对扑克牌的排序就可以转换为对整数的排序。
输入亚当和夏娃各自所取的K张牌后,将每张牌按牌面转换为一个整数值并分别保存到数组A和数组E中,然后将数组A和E分别按从小到大的顺序排列好。
为了求出夏娃的最多得分,可以采用贪心法求解。
贪心策略是:如果夏娃当前最大的牌可以赢亚当最大的牌,那么让这两张牌比大小,赢得1分;如果夏娃当前最小的牌能赢亚当最小的牌,那么让这两牌比大小,赢得1分;如果上面两个条件都不满足,就让夏娃当前最小的牌和亚当当前最大的牌比大小,让亚当得1分,但用掉其最大的牌。
(2)源程序。
#include <stdio.h> void sort(int a[],int n) { int i,j,tmp; for (i=0;i<n-1;i++) for (j=0;j<n-1-i;j++) if (a[j]>a[j+1]) { tmp=a[j]; a[j]=a[j+1]; a[j+1]=tmp; } } int value(char card[]) { int p,s; if (card[0]>='2' && card[0]<='9') p=card[0]-'0'; else if (card[0]=='T') p=10; else if (card[0]=='J') p=11; else if (card[0]=='Q') p=12; else if (card[0]=='K') p=13; else if (card[0]=='A') p=14; if (card[1]=='C') s=0; else if (card[1]=='D') s=1; else if (card[1]=='S') s=2; else if (card[1]=='H') s=3; return 4*p+s; } int main() { int n; scanf("%d",&n); while (n--) { int a[27],e[27]; int k; scanf("%d",&k); char card[3]; int i; for (i=0;i<k;i++) { scanf("%s",card); a[i]=value(card); } for (i=0;i<k;i++) { scanf("%s",card); e[i]=value(card); } sort(a,k); sort(e,k); int cnt=0; int aBegin=0,aEnd=k-1; int eBegin=0,eEnd=k-1; while (eBegin<=eEnd) { if (e[eBegin]>a[aBegin]) { aBegin++; eBegin++; cnt++; } else if (e[eEnd]>a[aEnd]) { aEnd--; eEnd--; cnt++; } else { eBegin++; aEnd--; } } printf("%d\n",cnt); } return 0; }
习题78
78-1 纸牌游戏
问题描述
Alice和她的朋友Bob正在玩一种纸牌游戏。这个游戏里要用到一副 2N张牌的套牌,编号从1到2N。Alice和Bob每个人各分得N张卡牌。接下来进行N轮比赛,Alice和Bob每轮各出一张牌。每一轮谁的牌编号更大,谁就赢得了本轮的胜利。
Alice已经预测了Bob的出牌顺序,请帮助Alice算出她最多能赢多少轮。
输入
第一行一个整数 N(1≤N≤5×104 )。
接下来N行,第i行一个整数,表示Bob第i轮出的牌。注意Alice手中的N张牌很容易从输入中推出。
输出
输出Alice最多能赢多少轮。
输入样例
3
1
6
4
输出样例
2
说明/提示
Alice手中拿着 2,3,5三张牌。
她第一轮出2,第二轮出3,第三轮出5,从而赢得一,三两轮。可以证明不存在更优的方案。
(1)编程思路。
定义数组int h[100005];初始值全部为0。依次输入Bob的N张卡牌的编号,对于每个编号x,置h[x]=1。
定义数组int a[50001],b[50001]分别保存Alice和Bob手里的卡牌,输入结束后,对数组h进行扫描,将h[i]=0的下标i依次保存到数组a中,这是Alice所取的卡牌;将h[i]=1的下标i依次保存到数组b中,这是Bob所取的卡牌。显然,数组a和数组b均已按卡牌的编号从小到大排列好了。
为了让Alice赢最多的轮次,可以采用贪心法求解。
贪心策略是:如果Alice当前编号最大的牌可以赢Bob当前编号最大的牌,那么让这两张牌比大小,Alice赢1轮;如果Alice当前编号最小的牌能赢Bob当前编号最小的牌,那么让这两牌比大小,Alice赢1轮;如果上面两个条件都不满足,就让Alice当前编号最小的牌和Bob当前编号最大的牌比大小,让Bob赢这1轮,但这轮用掉了Bob编号最大的牌。
(2)源程序。
#include <stdio.h> int main() { int h[100005]={0},a[50001],b[50001]; int n; scanf("%d",&n); int i; for (i=1;i<=n;i++) { int x; scanf("%d",&x); h[x]=1; } int ak=1,bk=1; for (i=1;i<=2*n;i++) { if (h[i]==0) a[ak++]=i; else b[bk++]=i; } int cnt=0; int aBegin=1,aEnd=n; int bBegin=1,bEnd=n; while (aBegin<=aEnd) { if (a[aBegin]>b[bBegin]) { aBegin++; bBegin++; cnt++; } else if (a[aEnd]>b[bEnd]) { aEnd--; bEnd--; cnt++; } else { aBegin++; bEnd--; } } printf("%d\n",cnt); return 0; }
78-2 严格递增的序列
问题描述
给定一个长度为n的序列,你可以将序列中某些元素各减去一个数,使得整个序列严格递增。
你需要求出所有减去的数的总和的最小值。
例如:有一个长度为3的序列 5,5,5;最优方案是 5-2,5-1,5即 3,4,5。这样所有减去的数的总和是 2+1=3,为最小值。
输入
输入第一行一个整数 n(1≤n≤100),表示序列的长度。
第二行n个整数,描述这个序列。
输出
输出一行一个整数,表示总和的最小值。
输入样例
4
5
3
7
5
输出样例
6
(1)编程思路。
要是序列严格递增,即序列的下一个数必须至少比前1个数大1,为了使所有减去的数的总和值最小,因此,贪心策略为:如果前1个数不小于后1个数,则把前1数减至比后1数小1即可。
具体处理时,从第n-1个数开始倒推处理到第1个数,即最后的第n个数不变。
若第i(i从n-1循环处理到1)个数比第i+1个数大(或相等),让第i个数等于第i+1个数-1,并累计它们的差值。
(2)源程序。
#include <stdio.h> int main() { int n; scanf("%d",&n); int a[105]; int i; for (i=1;i<=n;i++) scanf("%d",&a[i]); int ans=0; for (i=n-1;i>=1;i--) if (a[i]>=a[i+1]) { ans+=a[i]-a[i+1]+1; a[i]=a[i+1]-1; } printf("%d\n",ans); return 0; }
78-3 卡牌游戏
问题描述
小明某天想到了一个卡牌游戏,游戏规则如下:
初始时小明的手中有自左向右排成一排的n 张卡牌,每张卡牌上有一个整数分值。
接下来,小明每次可以选取卡牌序列最左边的连续若干张卡牌(至少2张),将它们替换为一张新卡牌。新卡牌将插入到序列的最左端,它的分值为本次操作中被替换掉的卡牌的分值之和。
初始时小明总分为0,每执行一次卡牌替换操作,新卡牌的分值将加到总分中。当序列长度为1时游戏结束,小明也可以在任意时刻结束游戏。
现在给出序列中各个卡牌的分值,请你来帮助小明计算他能够获得的最高总分是多少?
输入
第一行一个正整数n,代表卡牌的数目。
接下来一行n个以空格分隔的整数,第 i个数字ai代表自左向右第i张卡牌的分值。
(1≤n≤105,ai≤105)
输出
仅一行一个整数表示答案。
输入样例
7
-4 3 0 7 -3 -5 -3
输出样例
9
样例解释
最优策略为,首先选择最左侧的四张卡牌,总分增加 (-4) + 3 + 0 + 7 = 6。此时小明选择的四张卡牌被替换为一张分值为6 的卡牌,且被放入序列最左侧,此时自左向右卡牌的分值为 6, -3, -5, -3。
再选择最左侧的两张卡牌,总分增加 6 + (-3) = 3,总分为9。此时小明选择的两张卡牌被替换为一张分值为3的卡牌,且被放入序列最左侧,此时自左向右卡牌的分值为 3, -5, -3。
此时无论如何操作均无法使总分继续增大,小明选择结束游戏。
(1)编程思路。
定义变量sum来保存输入的卡牌分值的前缀和,初始化sum=第1张牌的分值,定义长整型变量ans保存能够获得的最高总分,初始值为0。
用循环从第2张卡牌开始依次读入第i张卡牌的分值x,并累加到sum上去,若sum>0,则将sum再累加到ans上,表示从第1张牌到第i张牌替换为一张分值为sum的新卡牌;若sum<=0,则不能增大总分值,因此不替换。
(2)源程序。
#include <stdio.h> int main() { int n; scanf("%d",&n); long long x,sum; scanf("%lld",&x); sum=x; long long ans=0; int i; for (i=2;i<=n;i++) { scanf("%lld",&x); sum+=x; if (sum>0) ans+=sum; } printf("%lld\n",ans); return 0; }