博主前两天在洛谷做了一道不错的题(题号在标题),现在分享一下自己对题目的看法。
题目
题目很简单,第一行输入人数 m 跟每个人的牌数 n,第二行输入John的牌的点数。要求输出John最少能赢的次数。
分析
怎么求John最少能赢的次数呢?最少赢的次数,即分析John出牌最差的情况。这里我们可以运用一种反向田忌赛马的思想切入这个问题。
在故事田忌赛马中,田忌用下等马对别人的上等马,再用自己较好的马对别人稍微差一点的马。而我们这里提到的反向田忌赛马的思想与这个类似。由题目可以知道,如果John要打出他手上的最大牌,只要有一个人打出的牌比他大,他这轮就没有获胜;而当John要打出的牌是全场最大的时候,别人就打出自己的最小牌从而保留自己的高点数牌。如果在总共为n轮的游戏中都以这种方式出牌,对John都是最不利的,再统计这种情况下John赢的次数,即可得到John最少赢的次数。
我们先做基本的输入如下
int m; //游戏人数m
int n; //每个人的纸牌数n
scanf_s("%d", &m);
scanf_s("%d", &n);
int all_card[50]; //John的所有牌
int i;
for (i = 0; i < n; i++) {
scanf_s("%d", &all_card[i]);
}
核心算法
因为牌的点数从1到n*m,总共进行n轮游戏,不难得出,按照以上的思路只有牌的点数为n(m-1)+1到nm即有前n个大点数的牌的时候,John才有取胜的可能 (可以试想两种极端的情况:1.John的牌均小于n(m-1)+1显然他此时获胜次数为0;2.John的牌从n(m-1)+1到nm,此时John每次都获胜) 。所以我们第一步就是从John所有的手牌里选出点数为n(m-1)+1到nm的牌。
int selected_card[50];
int index1; //index1记录经过选择后的牌的个数
for (i = 0,index1=0; i < n; i++) {
if (all_card[i] > (m - 1) * n) {
selected_card[index1] = all_card[i];
index1++;
}
}
完成牌的挑选之后,核心问题就是怎么实现所谓的反向田忌赛马。通过以上分析,我们知道当一部分点数n(m-1)+1到nm的牌在John手里的时候,另外一部分必然在别人的手里。在这里别人手里的大点我们同意用数组
int othercard[50];
进行表示。下面就是怎么出牌了,联系田忌赛马,我们可以想到可以进行这样的操作: