I am a teacher!

导航

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;
}

posted on 2022-03-07 10:49  aTeacher  阅读(1947)  评论(0编辑  收藏  举报