(Week 13)综合训练(C++)

[Daimayuan]走楼梯2(C++,DP)

楼梯有 n n n 阶,上楼可以一步上一阶,也可以一步上二阶。

但你不能连续三步都走两阶,计算走到第 n n n阶共有多少种不同的走法。

输入格式

一行,一个数字,表示 n n n

输出格式

输出走楼梯的方式总数。

样例输入
6
样例输出
12
数据规模

对于 100 100 100%的数据,保证 n ≤ 50 n≤50 n50

解题思路:

需要遍历每一种情况,很容易想到DP

如果没有限制“不能连续三步都走两阶”,本题就是简单的兔子数列

dp[0] = 1;//简单的初始化条件,台阶数为0,只有一种可能

for (int i = 1; i <= max_step; i++)
	dp[i] = dp[i - 1] + dp[i - 2];

多了限制,自然就少了可能

那么尝试用排除法

需要排除走 6 6 6 8 8 8 10 10 10 14 14 14 . . . ... ...,显然需要排除的情况太多了

所以我们采用正难则反的思路,正面求解

对于每一步,我们有三种选择(只有三种情况,不难证明): 1 1 1 2 2 2 4 4 4

但是需要考虑限制,所以有以下几种情况

(1)当前走了 1 1 1步,到达此处的方式不受限制

(2)当前走了 2 2 2步,所以我们只能通过走 1 1 1步的方式到达此处

(3)当前走了 4 4 4步,所以我们只能通过走 1 1 1步的方式到达此处

很简单,于是我们根据这个思路写出代码

int dp[max_step + 1] = { 0 };//存储数据含义:到达当前位置之后的可能数
bool disable[max_step + 1][3] = { false };//限制标记
int step[3] =  { 1,2,4 };

dp[0] = 1;//简单的初始化条件,台阶数为0,只有一种可能

for (int i = 1; i <= max_step; i++) {
	for (int j = 0; j < 3; j++) {
		if (!disable[i - step[j]][j]) {
			dp[i] += dp[i - step[j]];
			if (j != 0) disable[i][1] = disable[i][2] = true;//只能通过走1步的方式到达
		}
	}
}

结束了?当然没有,这个思路是有一点瑕疵的

假如当前有 4 4 4级台阶,有几种方法?

很快你就会回答有 5 5 5种,但是计算机会回答有 4 4 4

那么是哪一种可能性被漏掉了呢?

1 1 1 1
2 1 1
1 2 1
1 1 2
2 2

是第三种可能消失了

因为我们被禁止跳 2 2 2级台阶到达dp[2],但是这不是应该被禁止的吗?

我们要禁止的是跳 2 2 2级、跳 4 4 4级连续选择

但是上面的方法会导致跳 2 2 2级之后跳 1 1 1级的可能也被去除了,所以我们修改代码,把跳 1 , 2 , 4 1, 2, 4 1,2,4级的情况分开存储

int dp[max_n + 1][3];//存储的数据含义:以方式j到达当前位置之后的可能数
int step[3] = { 1,2,4 };

dp[0][0] = dp[0][1] = dp[0][2] = 1;

for (int i = 1; i <= max_step; i++) {
    for (int j = 0; j < 3; j++) {
        dp[i][0] += dp[i - step[j]][j];
        if (j == 1) dp[i][j] += dp[i - step[0]][0];
        else if (j == 2) dp[i][j] += dp[i - step[0]][0];
    }
}

最后,AC代码如下

#include <iostream>
#include <string.h>
using namespace std;
const int max_n = 50;
const int bias = 4;//加上偏置量,防止数组越界

long long dp[max_n + 1 + bias][3];
int step[3] = { 1,2,4 };


int main() {
	int n;
	cin >> n;
	dp[0 + bias][0] = dp[0 + bias][1] = dp[0 + bias][2] = 1;
	for (int i = 1 + bias; i <= n + bias; i++) {
		for (int j = 0; j < 3; j++) {
			dp[i][0] += dp[i - step[j]][j];
			if (j == 0) {
				dp[i][1] += dp[i - step[j]][j];
				dp[i][2] += dp[i - step[j]][j];
			}
		}
	}
	cout << dp[n + bias][0];
	return 0;
}

[Daimayuan] 任务分配(C++,DP)

你有 n n n个任务,其中第 i i i个任务,在 s i s_i si开始, e i e_i ei时刻结束,如果做这个任务,你能获得 w i w_i wi的收益。

但是你在一个时刻只能做一个任务,问选择哪些任务,能让你的收益尽量大。

注意:你在上一个任务结束后马上开始下一个任务是可以的。

输入格式

第一行一个整数 n n n

接下来 n n n行,每行三个整数 s i s_i si, e i e_i ei, w i w_i wi

输出格式

一个数,表示答案。

样例输入
3
1 3 100
2 4 199
3 5 100
样例输出
200
数据规模

对于所有数据,保证 1 ≤ n ≤ 1 0 3 , 1 ≤ s i < e i ≤ 1 0 3 , 1 ≤ w i ≤ 1 0 5 1≤n≤10^3,1≤s_i<e_i≤10^3,1≤w_i≤10^5 1n103,1si<ei103,1wi105

解题思路

本题是一道非常规的、同时也是非常好的动态规划题目,具体的“非常规”体现在DP的思路上,接下来说明解题思路

题意抽象来说就是给了起止 1 1 1~ 1000 1000 1000区间范围内的多条线段,每条线段有自己的权重,选择的线段不能重叠,问如何选使得得到的线段权重最大

很明显,这是一道动态规划问题,那么如何动态规划?

动态规划的关键在于如何从子问题推出更复杂问题的答案

首先我们尝试确定一个子问题的状态:

(1)当前可以得到的最大权重

(2)当前的截止时间

(3)当前考虑的线段数量

先试一下根据(3)展开动态规划,也就是按照常规背包问题的思路

我们会发现我们无法从只考虑前i - 1个物品的情况推导出前i个物品的情况

比如说有如下三条线段

1 2 10
1 3 20
2 4 100

只考虑前两条线段的时候我们可以得到的最大值是20,也就是选择第2条线段

但是我们如果选择第2条线段,我们就不能得到选择13线段的最优解

所以我们换一个角度考虑,有关线段覆盖的问题,终止时间是很重要的

如果到终止时间i之前的最大值是已知的,那么我们就只需要考虑i之后的最大值了

从这里我们可以看出最优子结构的存在

那么尝试一下动态规划,还是这样三条线段

1 2 10
1 3 20
2 4 100

考虑截止时间为3时的最大值,是20

好像我们还是不能从截止时间为3时的最大值推出截止时间为4时的最大值

所以说这道题是一道“非常规”的动态规划问题

我们不是在尝试用截止时间为i - 1时的情况推导出截止时间为i时的情况

而是尝试用截止时间为i时的情况去更新之后所有情况的最大值

for (int i = 1; i < max_se; i++) {
	dp[i + 1] = max(dp[i + 1], dp[i]);
	for (int j = 1; j <= n; j++) {
		if (i == tasks[j].s) {
			dp[tasks[j].e] = max(dp[tasks[j].e], dp[i] + tasks[j].w);
		}
	}
}

我们实现了在到达dp[i]之前,尝试过了所有可能的情况,保证了dp[i]是最优解

然后去尝试所有起始时间是i的任务,更新之后的所有元素

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_n = 1e3;
const int max_se = 1e3;
const int max_w = 1e5;

struct task { int s, e, w; }tasks[max_n + 1];
int dp[max_se + 1], n;


int main() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		cin >> tasks[i].s >> tasks[i].e >> tasks[i].w;
	}

	for (int i = 1; i < max_se; i++) {
		dp[i + 1] = max(dp[i + 1], dp[i]);
		for (int j = 1; j <= n; j++) {
			if (i == tasks[j].s) {
				dp[tasks[j].e] = max(dp[tasks[j].e], dp[i] + tasks[j].w);
			}
		}
	}
	cout << dp[max_se] << endl;
	return 0;
}

[Daimayuan]走路(C++,DP)

有一条很长的数轴,一开始你在 0 0 0的位置。接下来你要走 n n n步,第 i i i步你可以往右走 a i a_i ai或者 b i b_i bi

n n n步之后, 0 0 0 m m m的每个位置,能不能走到?

输入格式

第一行,两个整数 n n n, m m m

接下来 n n n行,每行两个整数 a i a_i ai, b i b_i bi

输出格式

一行,一共 m + 1 m+1 m+1个数,每个数都是01表示能否走到,数字之间不用空格隔开。

输入样例
3 10
1 2
2 6
3 3
输出样例
00000011001
数据规模

对于所有数据,保证 1 ≤ n ≤ 100 , 1 ≤ m ≤ 105 , 1 ≤ a i , b i ≤ 1000 1≤n≤100,1≤m≤105,1≤a_i,b_i≤1000 1n100,1m105,1ai,bi1000

解题思路:

如果不是像我一样把题看错了的话应该知道这就是一道简单的动态规划问题

一定要看到是“往右走 a i a_i ai或者 b i b_i bi“而不是”往右走 a i a_i ai或者往左走 b i b_i bi

那么接下来讨论如何实现代码

题意还是有一点模糊的,问的不是你在n步之内能够到达的所有地方,而是你在第n步能够到达的所有地方

显然需要遍历每一种情况,采用动态规划来实现

动态规划最重要的自然是明确每一步需要做什么,也就是状态转移方程:

dp[i][j] = dp[i - 1][j - step[i][0]] || dp[i - 1][j - step[i][1]]

解释一下:

dp[i]代表你当前的是第i

dp[i][j]代表你当前的位置为j

step[i][0]step[i][1]代表你可以向右走几步

也就是说,只要你在第i - 1步时能够到达j - step[i][0]或者j - step[i][1],你就能到达dp[i][j]

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_n = 100;
const int max_m = 1e5;
const int max_ab = 1e3;

int n, m;
int steps[max_n + 1][2];
bool dp[max_n + 1][max_m + 1];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> steps[i][0] >> steps[i][1];
	}
	dp[0][0] = true;//初始化
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			if (j - steps[i][0] >= 0)
				dp[i][j] = (dp[i][j] || dp[i - 1][j - steps[i][0]]);
			if (j - steps[i][1] >= 0)
				dp[i][j] = (dp[i][j] || dp[i - 1][j - steps[i][1]]);
		}
	}
	for (int i = 0; i <= m; i++)
		cout << dp[n][i];
	return 0;
}

[Daimayuan]特殊的正方形(C++)

输入 n n n,输出 n n n n n n列的由+.组成的正方形,其中最外面一圈全是+,第二圈全是.,…,对于第 i i i圈,如果 i i i是奇数,那么全是+,否则全是.

输入格式

一行,一个整数 n n n

输出格式

n n n行,为满足题目要求的正方形。注意不要有行末空格。

样例输入
10
样例输出
++++++++++
+........+
+.++++++.+
+.+....+.+
+.+.++.+.+
+.+.++.+.+
+.+....+.+
+.++++++.+
+........+
++++++++++
数据范围

对于 100 100 100%的数据,保证 2 ≤ n ≤ 100 2≤n≤100 2n100

解题思路:

虽然本题数据范围不大,可以直接实现数组存储,然后输出

但这里仍然采用无存储的方式实现

二维图形输出,自然采用二重循环实现

关键在于如何调整输出.还是+

这里通过两条对角线来实现调整

每次输出前反转两条对角线之间要输出的符号,我们具体模拟一下来说明

初始化输出模式..........

然后我们来到第一行,反转之后的输出模式++++++++++

然后我们来到第二行,反转之后的输出模式+........+

然后我们来到第三行,反转之后的输出模式+.++++++.+

是不是很好理解?但是没有结束,我们需要特殊处理

因为当前每一次循环执行的动作是简单的:反转,然后输出

但是如果这样的话,前半段输出是对的,后半段就会出现错误,如下

++++++++++
+........+
+.++++++.+
+.+....+.+
+.+.++.+.+
+.+....+.+
+.++++++.+
+........+
++++++++++
..........

所以我们稍微调整一下:

(1)前半段:反转,然后输出

(2)后半段:输出,然后反转

最后,AC代码如下

#include <iostream>
using namespace std;
const int max_n = 100;

char pattern[max_n];	//每行的输出模式
bool save[max_n];		//处理模式的调整


//反转
inline void reverse(char& c) {
	if (c == '.') c = '+';
	else c = '.';
}


int main() {
	int n;
	bool change = false;
	cin >> n;
	for (int i = 0; i < n; i++) pattern[i] = '.';//初始化
	for (int i = 0; i < n; i++) {
		if (save[i]) {//后半段:先输出,后反转
			cout << pattern << endl;
			for (int j = 0; j < n; j++) {
				if (i == j || i + j == n - 1) {//判断对角线
					save[i] = save[n - i] = true;
					change = !change;
					reverse(pattern[j]);
					j++;
					while (change) {
						reverse(pattern[j]);
						if (i == j || i + j == n - 1) {//判断对角线
							change = !change;
						}
						else {
							j++;
						}
					}
				}
			}
		}
		else {//前半段:先反转,后输出
			int j;
			for (j = 0; j < n; j++) {
                //特殊判定:如果对角线交于一点
				if (i == j && i + j == n - 1) {
					reverse(pattern[i]);
				}
				else {//常规
					if (i == j || i + j == n - 1) {//判断对角线
						save[i] = save[n - 1 - i] = true;
						change = !change;
						reverse(pattern[j]);
						j++;
						while (change) {
							reverse(pattern[j]);
							if (i == j || i + j == n - 1) {//判断对角线
								change = !change;
							}
							else {
								j++;
							}
						}
					}
				}
			}
			cout << pattern << endl;
			if (i == n / 2) {//特殊判定:如果对角线交于一点
				reverse(pattern[i]);
			}
		}
	}
	return 0;
}

[Daimayuan]订单编号(C++,STL)

小缘开了一家公司,生意很好,每天都会收到很多订单,自动交易系统会自动给这些订单生成没有重复的订单编号。但是有一天,系统出现了未知的错误,导致当天的订单编号可能有重复的,这可把小缘急坏了。你可以帮助小缘按照规则给这些订单重新编号吗?

按照时间先后顺序给出 N N N 个正整数作为原订单编号,你需要按照规则依次赋予这些订单新的编号,对于任意一个订单,要找到大于等于其原订单编号且未被使用过的(没有被之前的订单作为新的订单编号)的最小整数,作为它的新订单编号。

例如: 原订单编号依次为 1 1 1 2 2 2 3 3 3 1 1 1,则新订单编号应该为 1 1 1 2 2 2 3 3 3 4 4 4 (前 3 3 3个订单的原订单编号都没有使用过,所以用其原订单编号即可,对于第四个订单,原订单编号为 1 1 1,而 1 1 1, 2 2 2, 3 3 3都已经被使用过,所以新订单编号为 4 4 4)。

输入格式

第一行输入一个整数 N N N ( 1 ≤ N ≤ 5 × 1 0 5 1≤N≤5×10^5 1N5×105)。

第二行输入 N N N 个数 a i a_i ai ( 1 ≤ a i ≤ 1 0 9 1≤a_i≤10^9 1ai109) 作为原订单编号。

输出格式

输出一行,包含 N N N 个整数为新的订单编号。

样例输入1
6
2 3 4 1 1 1
样例输出1
2 3 4 1 5 6
样例输入2
3
1000000000 1000000000 1000000000
样例输出2
1000000000 1000000001 1000000002
样例输入3
6
4 5 1 2 1 1
样例输出3
4 5 1 2 3 6
解题思路:

本题考察对STL的掌握

采用set+pair维护未使用序号的区间来解决本题

主要是利用setlower_bound方法:传入一个val,返回集合中大于等于val的最小值;如果是pair数据类型,默认先比较first再比较second

AC代码如下

#include <stdio.h>
#include <stdlib.h>
#include <set>
using namespace std;
const int max_a = 1e9;
const int max_n = 5e5;

int n, a;
set<pair<int, int>>s;


int main() {
	s.insert(pair<int, int>(2 * max_a, 1));
	scanf("%d", &n);
	int out_temp = 0;
	for (int i = 0; i < n; i++) {
		scanf("%d", &a);
		auto ret = s.lower_bound({ a,0 });
		if (ret->second < a) {
			printf("%d ", a);
			s.insert(pair<int, int>(a - 1, ret->second));
			if (ret->first != a)
				s.insert(pair<int, int>(ret->first, a + 1));
			s.erase(ret);
		}
		else {
			printf("%d ", ret->second);
			if (ret->first != ret->second)
				s.insert(pair<int, int>(ret->first, ret->second + 1));
			s.erase(ret);
		}
	}
	return 0;
}

注意,这里用C语言的输入输出而不用C++coutcin,否则会超时

[OJ]饿饿 饭饭(C++,二分)

n n n个同学正在排队打饭,第 i i i个同学排在从前往后第 i i i个位置。但是这天食堂内只有一个食堂阿姨,为了使同学们都能尽快的吃上饭,每一个同学在打完一份饭之后就会排在队伍的末尾先吃着打到的饭,我们知道第 i i i个同学的饭量为 a i a_i ai,也就是说第 i i i个同学要吃 a i a_i ai份饭才能吃饱,当一位同学吃饱后,他就会立刻离开食堂,不会排在队伍的末尾。食堂阿姨想知道,在打完k份饭之后,队伍的样子是怎样的,但是食堂阿姨数学不太好,想让你帮忙想想办法。

输入格式

第一行给出两个整数 n n n k k k

第二行给出 n n n个整数 a 1 a_1 a1, a 2 a_2 a2,… a n a_n an

输出格式

如果食堂阿姨打饭数少于 k k k,请输出-1

否则按照队伍顺序输出每一个同学的编号。

样例输入1
3 3
1 2 1
样例输出1
2
样例输入2
4 10
3 3 2 1
样例输出2
-1
样例输入3
7 10
1 3 3 1 2 3 1
样例输出3
6 2 3
数据规模

数据保证 1 ≤ n ≤ 105 , 0 ≤ k ≤ 1014 , 1 ≤ a i ≤ 1 0 9 1≤n≤105, 0≤k≤1014, 1≤a_i≤10^9 1n105,0k1014,1ai109

解题思路:

先采用二分搜索找出需要多少轮才能分完所有的饭,如果找不到输出-1

反之先进行ret - 1轮,然后模拟最后一轮,最后输出即可

代码如下

#include <stdio.h>
#include <stdlib.h>
#include <queue>
using namespace std;
const long long max_n = 1e5;
const long long max_k = 1e14;
const long long max_a = 1e9;

long long n, k;
long long students[max_n + 1];
queue<pair<long long, long long>>ans;


bool judge(long long loop) {
	long long sum = 0;
	for (int i = 1; i <= n; i++) {
		sum += min(students[i], loop);
	}
	return sum <= k;
}


long long bin_search() {
	long long l = 0, r = k + 2;
	while (l + 1 != r) {
		long long m = (l + r) / 2;
		if (judge(m)) l = m;
		else r = m;
	}
	return l;
}

int main() {
	scanf("%lld %lld", &n, &k);
	for (int i = 1; i <= n; i++) scanf("%lld", students + i);
	long long ret = bin_search();


	for (int i = 1; i <= n; i++) {
		k -= min(ret, students[i]);
		students[i] -= min(ret, students[i]);
		if (students[i]) ans.push(pair<long long, long long>(i, students[i]));
	}

	if (ret == k + 1 && k > 0) {
		printf("-1");
		return 0;
	}

	//模拟最后一轮
	pair<long long, long long>temp;
	while (k) {
		temp = ans.front(); ans.pop();
		temp.second--; k--;
		if (temp.second) {
			ans.push(temp);
		}
	}

	while (!ans.empty()) {
		printf("%d ", ans.front());
		ans.pop();
	}
	return 0;
}

上面代码唯一的问题就是测试点 6 6 6会莫名其妙地RE

[OJ]简单分数统计(C++,字符串)

N N N 个好朋友在 c o d e f o r c e s codeforces codeforces上参加一场包含 M M M 个题目的比赛, 比赛期间 c o d e f o r c e s codeforces codeforces网站一共有 k k k 次提交。

已知每个题目的分数,

但是由于他们只能查到在比赛期间 c o d e f o r c e s codeforces codeforces总共的提交记录(其他用户提交的其他题目记录也包含在内, 即存在不属于该场比赛的题目),

所以想请你编写一个程序算出他们每个人的分数。

输入格式

第一行三个整数 N N N, M M M, K K K 分别表示好朋友的个数, 题目的个数, 和提交的总次数(其中 0 < N , M , K < = 200 0<N,M,K<=200 0<N,M,K<=200)。

接下来 N N N 行 第 i i i 行输入为第 i i i 个人的 i d id id

接下来 M M M 行 第 j j j 行输入为第 j j j 个题目的名称和分数,

接下来 K K K 行 第 k k k 行输入为第 k k k 次提交的提交者 i i i, 题目名称和结果(“WA” 或 “AC”, 如果"AC"代表通过这个题目, 提交者获得对应分数)。

注: 题目名称和id均为仅包含英文字母和数字的字符串, 题目分数为小于等于 1 e 6 1e6 1e6 的正整数. 每一行的多个输入之间用空格隔开。

所有输入的字符串长度 l e n g t h length length 满足 0 < l e n g t h ≤ 500 0<length≤500 0<length500

所有用户id和题目名称不存在重名, 用户AC了某个题之后之后不会再重复提交该题, 好朋友们只会提交属于比赛的题目。

输出格式

输出 N N N 行, 第 i i i 行输出第 i i i 个人的名字和对应分数 (名字和分数用空格隔开)。

样例输入
2 2 4
GabrielPessoa
beza
metebronca 100
geometry 200
beza metebronca AC
ffern numbertheory AC
GabrielPessoa geometry WA
beza geometry AC
样例输出
GabrielPessoa 0
beza 300
样例解释

b e z a beza beza 过了 m e t e b r o n c a metebronca metebronca g e o m e t r y geometry geometry 拿到 300 300 300 分。

G a b r i e l P e s s o s GabrielPessos GabrielPessos 没有过题, 所以是 0 0 0 分。

还有一些其他选手提交的其他题目忽略不计。

解题思路

一道考察字符串的题目

对于提交者,用map转换id为数组索引号

对于题目,用map转换名字为分数

然后判断是否AC累加求和即可

AC代码如下

#include <iostream>
#include <map>
using namespace std;
const int max_n = 200;
const int max_m = 200;
const int max_k = 200;

int n, m, k, sc;
string name, ques, res;
map<string, int>id2idx;
map<string, int>ques2score;
long long ans[max_n + 1];
string names[max_n + 1];


int main() {
	cin >> n >> m >> k;
	for (int i = 1; i <= n; i++) {
		cin >> names[i];
		id2idx.insert(pair<string, int>(names[i], i));
	}
	for (int j = 1; j <= m; j++) {
		cin >> ques >> sc;
		ques2score.insert(pair<string, int>(ques, sc));
	}
	map<string, int>::iterator ret;
	for (int i = 0; i < k; i++) {
		cin >> name >> ques >> res;
		ret = id2idx.find(name);
		if (ret != id2idx.end() && res == "AC") {
			ans[ret->second] += ques2score[ques];
		}
	}
	for (int i = 1; i <= n; i++) {
		cout << names[i] << ' ' << ans[i] << endl;
	}
	return 0;
}

[OJ]Alice的德州扑克(C++,字符串)

德州扑克是目前世界上最流行的扑克游戏,全世界有众多相关的比赛,例如是 WSOP,WPT,EPT等,也让这款游戏的玩法变得层出不穷,丰富多变。 不要被简单的游戏规则而误导,复杂多变的比赛状况,让这款游戏在高水平的竞技中会变得非常复杂,这也让人们为德州扑克给出了这样一句评价 ”用一刻就能学会,但要用一生才能掌握” 。

现在我们并不在乎游戏规则是什么,因为 Alice 是一个德州扑克高手,他对于德州扑克的规则烂熟于心,不过他每次都记不得牌型的大小关系,他知道你是一个编程高手,所以他想让你帮他写一个程序:输入五张牌的大小和花色,输出这五张牌能组成的最大牌型.你能帮帮他吗?

为了降低你的编程难度,我们规定:

  1. 输入的牌都是来源于同一副扑克牌
  2. 输入的牌的点数都是非递减的
  3. 所有花色没有大小之分

下面给出各牌型,(从大到小)

  1. 皇家同花顺(ROYAL FLUSH):五张顺连的牌(点数连续单调递增),且最大的一张牌是A(Ace),并且五张牌的花色相同
  2. 同花顺(STRAIGHT FLUSH):五张顺连的牌(点数连续单调递增),不规定最大的一张牌是A(Ace),并且五张牌的花色相同
  3. 四条(FOUR OF A KIND):至少四张牌的点数相同
  4. 葫芦(FULL HOUSE):至少三张牌的点数相同,并且除此之外还有两张牌的点数相同
  5. 同花(FLUSH):五张牌的花色都相同
  6. 顺子(STRAIGHT):五张顺连的牌(点数连续单调递增),不要求五张牌的花色相同
  7. 特别注意:由于 Alice 是个谨慎的人,所以比 三条(THREE OF A KIND) (包括三条) 小的牌型 Alice 不在乎他们的大小关系,你只需要告诉 Alice 弃牌就行
输入格式

输入两行,每行五个数字,第一行的第 i i i 个字符表示第 i i i 张扑克的点数,

第二行的第 i i i 个数字表示第 i i i 张扑克花色。(保证输入的牌的点数是非递减的,且所有输入均合法)

点数和对应输入的数字:

  • 2 − 10 2−10 210 对应 2 − 10 2 - 10 210
  • J ( J a c k ) J(Jack) J(Jack) 对应 11 11 11
  • Q ( Q u e e n ) Q(Queen) Q(Queen) 对应 12 12 12
  • K ( K i n g ) K(King) K(King) 对应 13 13 13
  • A ( A c e ) A(Ace) A(Ace) 对应 14 14 14

花色和对应输入的数字:

  • 黑桃$ (Spades)$ 对应 1 1 1
  • 方片 ( D i a m o n d s ) (Diamonds) (Diamonds) 对应 2 2 2
  • 红桃 ( H e a r t s ) (Hearts) (Hearts) 对应 3 3 3
  • 梅花 ( C l u b s ) (Clubs) (Clubs) 对应 4 4 4
输出格式

输出这五张牌能组成的最大牌型。

  • 如果最大是皇家同花顺输出 “ROYAL FLUSH”
  • 如果最大是同花顺输出 “STRAIGHT FLUSH”
  • 如果最大是四条输出 “FOUR OF A KIND”
  • 如果最大是葫芦输出 “FULL HOUSE”
  • 如果最大是同花输出 “FLUSH”
  • 如果最大是顺子输出 “STRAIGHT”
  • 如果最大的牌型小于等于三条输出"FOLD",劝 Alice 弃牌
  • 输出不包括引号
样例输入1
10 11 12 13 14
1 1 1 1 1
样例输出1
ROYAL FLUSH
样例输入2
10 11 12 13 14
1 2 1 3 4
样例输出2
STRAIGHT
样例输入3
6 6 6 7 7
1 2 3 1 3
样例输出3
FULL HOUSE
样例输入4
3 3 6 6 9
1 2 1 2 1
样例输出4
FOLD
解题思路

这里将要说明的代码的适用范围要比题目所给出的更广

因为我题读错了QAQ

所以只是想要解决原题问题的可以划过去了

改动了以下几点:

(1)输入两行,以换行符结尾

(2)第一行输入若干个扑克牌点数,分别是 2 2 2~ 10 10 10 J J J Q Q Q K K K A A A

首先这是一道模拟问题

我们可以注意到,不同 c o m b o combo combo之间是有包含关系的,而且所有 c o m b o combo combo由五种条件组合而成:

(1)顺子:五张连续

(2)同花:五张同花

(3) 2 2 2张、 3 3 3张、 4 4 4张点数相同

所以我们的判定自然也有顺序,这样就可以简化问题

输入点数之后我们就可以寻找(1)和(3)了

之后判断是否有顺子、葫芦和四条

然后输入点数,寻找(2)

当且仅当顺子和同花已经找到的情况下,我们继续寻找最大的两种 c o m b o combo combo

最后输出最大的 c o m b o combo combo即可

//Alice的德州扑克
#include <iostream>
#include <string>
#include <map>
using namespace std;
const int flower_num = 4;
const int max_card_num = 14;

map<string, int>str2point;
bool cards[flower_num + 1][max_card_num + 1];	//cards
int flower_sum[flower_num + 1];					//card flower sum
int card_sum[max_card_num + 1];					//card point sum
bool same_flower_nlt5[flower_num + 1];			//same flower no less than 5
bool combo[6];									//combo
string ans[6] = {								//combo2sting
	"STRAIGHT", "FLUSH", "FULL HOUSE",
	"FOUR OF A KIND", "STRAIGHT FLUSH", "ROYAL FLUSH"
};


//card2point
void init() {
	str2point.insert(pair<string, int>("2", 2));
	str2point.insert(pair<string, int>("3", 3));
	str2point.insert(pair<string, int>("4", 4));
	str2point.insert(pair<string, int>("5", 5));
	str2point.insert(pair<string, int>("6", 6));
	str2point.insert(pair<string, int>("7", 7));
	str2point.insert(pair<string, int>("8", 8));
	str2point.insert(pair<string, int>("9", 9));
	str2point.insert(pair<string, int>("10", 10));
	str2point.insert(pair<string, int>("J", 11));
	str2point.insert(pair<string, int>("Q", 12));
	str2point.insert(pair<string, int>("K", 13));
	str2point.insert(pair<string, int>("A", 14));
}


int main() {
	//initialize
	init();

	//input card point
	string str;
	char c = '\0';
	while (c != '\n' && c != '\r') {
		cin >> str;
		c = getchar();
		card_sum[str2point[str]]++;
	}

	//STRAIGH, FLUSH and FOUR OF A KIND
	//initialize
	bool same_point[3] = { false };
	int last_point = 0, conseq = 0, i = 2;
	//search for combo
	for (; i <= max_card_num; i++) {
		//for STRAIGHT
		if (card_sum[i]) {
			if (last_point + 1 == i) {
				conseq++;
				if (conseq >= 5) {
					combo[0] = true;//STRAIGHT
					break;
				}
			}
			else conseq = 1;

			last_point = i;
		}
		//for FULL HOUSE and FOUR OF A KIND
		if (card_sum[i] >= 4 && !same_point[2])
			same_point[2] = true;
		else if (card_sum[i] >= 3 && !same_point[1])
			same_point[1] = true;
		else if (card_sum[i] >= 2 && !same_point[0])
			same_point[0] = true;
	}
	//FOUR OF A KIND
	if (same_point[2]) combo[3] = true;
	//FULL HOUSE
	if (int(same_point[2]) + int(same_point[1]) + int(same_point[0]) >= 2) combo[2] = true;

	//initialize
	int flower;

	//input card flower
	for (int i = 2; i <= max_card_num; i++) {
		while (card_sum[i]) {
			cin >> flower;
			cards[flower][i] = true;
			card_sum[i]--;
			flower_sum[flower]++;
		}
	}

	//search for FLUSH
	for (int i = 1; i <= flower_num; i++) {
		if (flower_sum[i] >= 5) {
			same_flower_nlt5[i] = true;
			combo[1] = true;//FLUSH
		}
	}

	//search for STRAIGHT FLUSH and ROYAL FLUSH
	if (combo[0] && combo[1]) {//have found STRAIGHT and FLUSH
		for (int i = 1; i <= flower_num; i++) {
			if (same_flower_nlt5[i]) {
				//initialize
				int j = 2;
				while (!cards[i][j]) j++;
				last_point = j; conseq = 1;
				//for STRAIGHT FLUSH and ROYAL FLUSH
				for (; j <= max_card_num; j++) {
					if (cards[i][j]) {
						if (last_point + 1 == j) {
							last_point = j;
							conseq++;
							if (conseq >= 5) {
								combo[4] = true;
								if (last_point == 14) {
									combo[5] = true;
								}
							}
						}
						else {
							last_point = j;
							conseq = 1;
						}
					}
				}
			}
		}
	}

	//output phase
	for (int i = 5; i >= 0; i--) {
		if (combo[i]) {
			cout << ans[i] << endl;
			return 0;
		}
	}
	cout << "FOLD" << endl;
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WitheredSakura_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值