2020.7.28 模拟赛 题解

T1 挖矿

题目描述

有N名矿工在挖矿。工厂预先给第i名矿工支付了Mi元工资,他每挖一吨矿需要消费Ki元头余下的钱不足Ki元,他就停止挖矿。他每挖一吨矿,工厂会立即奖励他2元钱。奖励的钱于挖矿的消费。

给出矿工的信息,请你计算一下矿工们总共可以挖出多少吨矿,以及哪个矿工挖的矿最多。

  • 输入格式
    第1行:1个整数N,表示矿工的人数(1 ≤ N ≤ 70)

    接下来2N行,每2行描述1名矿工。第1行是一字符串(长度不超过20个字符),表示矿工的姓名,第2行 2个整数,分别表示Ki(12 ≤ Ki ≤ 400)和Mi(1 ≤ Mi ≤ 10000)

  • 输出格式
    第1行:1个整数,表示矿工们总共可以挖出多少吨矿

    第2行:1个字符串,表示挖矿最多的矿工的姓名。如果多个矿工挖得一样多,输出最靠前的1个人。

样例输入
4
Caterpie
12 33
Weedle
12 42
Pidgey
12 47
Rattata
25 71
样例输出
14
Weedle
分析

这好像是一道原题

我们只需要每输入一个人的信息,就根据题意模拟一遍,求得这个人最终能挖多少矿。并把它累加进sum。

如果当前这个人挖的矿比我们记录的挖矿最多的人挖的还要多,就保存一下这个人的名字

AC代码
#include <cstdio>

const int MAXN = 75; // 名字最长长度是75
const int MAXM = 25; // 最多有20个人
char name[MAXM][MAXN];

int main() {
	int n;
	scanf ("%d", &n);
	int ma = 0, sum = 0, index = 1;
	// ma表示最多挖矿数,sum表示总挖矿数,index表示挖的最多的那个人是第几个人
	for(int i = 1; i <= n; i++) {
		int k, m;
		// k表示挖一份矿需要的钱,m表示这个人一开始有多少钱
		scanf ("%s", name[i]);
		// 输入名字
		scanf ("%d %d", &k, &m);
		int t = 0;
		// 表示这个人的挖矿数
		while(m >= k) { // 如果目前有的钱能再挖一份矿
			m -= k; // 减去挖一份矿需要的钱
			t++; // 这个人的挖矿数加一
			m += 2;
			// 返利
		}
		if(t > ma) { // 如果是目前最多的挖矿数,记录下来
			index = i;
			ma = t;
		}
		sum += t;
	}
	printf("%d
", sum);
	printf("%s", name[index]);
	return 0;
}

T2 最小乘积

题目描述

给出0~9这10个数字的个数,放在数组A中。A[0]表示数字0的个数,A[1]表示数字1的个数,...,A[9]表示数字9的个数。

你要用这些数字构造整数A和B,A恰好有W1位,B恰好有W2,允许A和B出现前导0。要求数字i在A和 B中出现的次数之和不超过A[i]。

数据保证数组A的元素之和至少为W1+W2

在所有的合法整数对A、B中,找出它们乘积最小的一对数。

  • 输入格式
    第1行:10个整数,表示数组A, 0 ≤ A[i] ≤ 20

    第2行:1个整数,表示W1 1 ≤ W1 ≤ 9

    第3行:1个整数,表示W2 1 ≤ W2 ≤ 9

  • 输出格式
    第1行:1个整数,表示A和B的最小乘积

样例输入
0 1 1 2 1 1 0 0 0 0
2
3
样例输出
3042
数据范围与提示

数组A描述的数字是这些: {1,2,3,3,4,5}. A必须是2位数,B必须是3位数,最优解是 A = 13和B = 234.

这时 A * B = 3042,这是最小的乘积。

分析

贪心。 这其实是一道小奥的经典题目

如何才能使乘积最小?很显然应该让两个因数都尽可能的小。所以这道题其实就是让我们分配一些数到两个长度固定的数使这两个数都尽可能的小。

如果两个数尽可能的小的话,就必须让它们的首位尽可能的小。首位满足后就应该让第二位尽可能的小,然后第三位……并且我们知道数位多的那一个数一定比数位少的那个数大,所以再分配的时候我们应该更“偏袒”数位大的那个数,使它尽可能的小

这样就不难想出贪心策略:

1.

假设一个有序数列a[1], a[2], a[3]...a[n - 1], a[n]。
一个长度为m的数A,一个长度为n的数B,且m>n。

那按照上面的逻辑,我们就应该把a[1]放在A的首位,a[2]放在B的首位,a[3]放在A的第二位,a[4]放在B的第二位……以此类推

2.
因为这道题是允许出现前导零的,所以当出现前导零时怎么办呢?

很简单,首先我们知道

当0 < a < b < c < d < e时
a * bcd < ab * cde

所以,应该把所有的零都放在A的前面,让B前面没有零

AC代码
#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN = 11; // 0 ~ 9
const int MAXM = 205; // 合理分配后的数最长是 10 * 20

int a[MAXN], num[MAXM];
// a数组表示各有多少个0~9
// num表示所有可以用的数的数组
int x[MAXN], y[MAXN];
// x表示的A,y表示的B
int len_x = 0, len_y = 0;
// x,y的长度

long long work() {
	long long t1 = 0;
	for(int i = 1; i <= len_x; i++)
		t1 = t1 * 10 + x[i]; // 求得x具体的值
	long long t2 = 0;
	for(int i = 1; i <= len_y; i++)
		t2 = t2 * 10 + y[i]; // 求得y具体的值
	long long ans = t1 * t2; 
	return ans;
	// 直接返回x*y的值
}

int main() {
	int n = 0;
	for(int i = 0; i <= 9; i++) {
		scanf ("%d", &a[i]); // 输入有多少个可用的数字i
		if(i != 0) // 如果i不等于0(0我们会在后面单独判断
			for(int j = 1; j <= a[i]; j++)
				num[++n] = i;	
			// 把可用数字“拉开”
	}
	int w1, w2;
	scanf ("%d %d", &w1, &w2);
	// w1表示A的长度,w2表示B的长度
	if(w1 > w2) swap(w1, w2); 
	// 必须保证w1<w2
	if(a[0] < w1) w1 -= a[0]; // 如果0的数量比A的长度小?那就把它全部放在A的前面
	else {
		printf("0
");
		// 相当于0的数量比A的长度大,则A全是0,所以答案就是0
		return 0;
	} 
	int j = 1;
	while(j <= n) { // 挨个分配
		if(len_x < w1) { 
			x[++len_x] = num[j];
			j++;
		}
		if(len_y < w2) {
			y[++len_y] = num[j];
			j++;
		}
		if(len_x == w1 && len_y == w2) break;
		// 如果达到了长度就跳出循环
	}
	printf("%lld", work());
	return 0;
}

T3 回文数组

题目描述

给定有N个整数的数组A,下标从1到N。如果对每一个下标i均满足A[i] =A[N-i+1],则称数组是回文的。例如,数组A={1,2,3,2,1}就是回文数组。如果数组A不是回文的,可以采用合并两个相邻元素的方法去得到回文数组。注意,每操作一次,数组的元素数量减少1。

例如,数组A={1,2,3}不是回文数组,但是通过合并A[1]和A[2],得到{3,3}就是回文数组了。显然,无论给出怎样的数组元素,最多经过N-1次操作,合并为一个数时,数组A一定是回文数组了。因此,本题一定有解。

然而问题来了:对于给定的数组A,最少经过多少次操作,能让A变成回文数组?

  • 输入格式
    第1行:1个整数N,表示数组A的元素个数

    第2行:N个空格分开的整数,表示数组A

  • 输出格式
    第1行:1个整数,表示最少的操作次数

样例输入
4
1 4 3 2
样例输出
2
数据范围与提示

1 ≤ N ≤ 10^6
1 ≤ A[i] ≤ 109

分析

还是贪心。。。

直接使用双指针即可。
定义双指针 i = 1,j = n,如果 a[i] == a[j] 的话就满足“回文数组”。

如果不相等,该怎么办呢?
很简单,每一次操作相当于就是把两端任意一端的某个数增大了。
而我们最后要想让两个端点的数相等,那我们是不是只需要每次将小的那一个调大即可?(也就是对小的那一个进行操作

AC代码
#include <cstdio>

const int MAXN = 1000005;
long long a[MAXN];

int main() {
	int n;
	scanf ("%d", &n);
	for(int i = 1; i <= n; i++)
		scanf ("%lld", &a[i]);
	int i = 1, j = n;
	int ans = 0;
	while(i < j) {
		if(a[i] == a[j]) {
		// 如果两端相等直接跳过
			i++;
			j--;
		}
		else if(a[i] > a[j]) {
		// 左端点比右端点大,就把右端点增大(操作一下
			a[j - 1] += a[j];
			j--; // 右端点左移
			ans++; // 操作次数加一
		}
		else {
		// 右端点比左端点大,就把左端点增大(操作一下
			a[i + 1] += a[i];
			i++; // 左端点右移
			ans++; // 操作次数加一
		}
	}
	printf("%d", ans);
	// 输出即可
	return 0;
}

T4 乌龟

题目描述

一只乌龟由于智商低下,它只会向左或向右走,不过它会遵循主人小h的指令:F(向前走一步),T(掉头)。

现在小h给出一串指令,由于小h有高超的计算能力,他可以马上知道乌龟最后走到哪里。为了难倒小h,他的好朋友小c就说,现在让你修改其中n个指令,使得乌龟移动到离起点最远的地方。(修改是指“T”变成“F”,或“F”变成“T”,可以对同一个指令多次修改)。乌龟一开始在0点

  • 输入格式
    第1行:一个字符串S代表指令

    接下来一行一个整数n,表示要修改的指令个数

  • 输出格式
    第1行:一个整数,表示乌龟所能移动到的最远距离。

样例输入
FFFTFFF
2
样例输出
6
数据范围与提示

1 ≤ s.size() ≤ 100
1 ≤ n ≤ 50

分析

大佬都说这是简单dp?我觉得DFS才是最简单的叭hhhhhh

DFS思路:

v[i][j][k][l]表示当前执行第i个指令,已修改了j个指令  
据0点的距离为k(k<0表示在0的左边,反之,在0的右边)
l 表示方向

对于每个指令会有两种情况

1.如果这个指令是“F”,我们可以选择改或不改
2.如果是“T“,我们也可以选择改或不改

每次修改后在维护一下新的到零点的距离,以及方向即可

AC代码
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAXN = 110;
const int MAXM = 55;
char s[MAXN];
int v[MAXN][MAXM][2 * MAXN][2];
// v[i][j][k][l]表示当前执行第i个指令,已修改了j个指令
// 据0点的距离为k(k<0表示在0的左边,反之,在0的右边 
// 以及方向l
int n, len, ans = 0;

void dfs(int i, int j, int dis, int flag) {
	if(v[i][j][dis][flag] == true) return ; // 这种方法已经遍历过了
	v[i][j][dis][flag] = true;
	if(i > len + 1 || j > n) return ;
	if(i == len + 1 && j == n) { // 更新答案 
		ans = max(ans, abs(dis));
		return ;
	}
	if(s[i] == 'T') 
		dfs(i + 1, j + 1, dis + (flag == 0 ? 1 : -1), flag);
	// 使用一次修改指令 T
	else
		dfs(i + 1, j, dis + (flag == 0 ? 1 : -1), flag);
	// 不用指令 F 
	if(s[i] == 'F')
		dfs(i + 1, j + 1, dis, flag == 0 ? 1 : 0);
	// 使用一次修改指令 F
	else dfs(i + 1, j, dis, flag == 0 ? 1 : 0); 
	// 不用指令 T 
	dfs(i, j + 2, dis, flag); // 改两次?等于没改 
}

int main() {
	scanf ("%s", s + 1);
	len = strlen(s + 1);	
	scanf ("%d", &n);
	dfs(1, 0, 0, 0);
	printf("%d
", ans);
	return 0;
}

反思

啊啊啊啊啊啊本来330的,考场上把大部分时间去怼最后一题了,导致只有一点点时间去复查前面的……结果T1,T2居然出现数组没够,循环没到边界等错误……
第一题还硬生生的为了省时间,把模拟打成了找规律……

所以在最后提醒大家

1.千万看好数据范围!不要每道题就是一个MAXN打过去,最好是对应每个数据范围不同的数组分别开MAXN,MAXL,MAXM……

2.一定要严格推算某些数最多能取到哪里,不要想当然!

3.不要妄想偷懒!

4.不要过度依赖评测机!不然一用Lemon就废(我觉得这是我现在的问题。

心是人的原动力,心的强大是没有边际的,集中一点,登峰造极。

原文地址:https://www.cnblogs.com/Chain-Forward-Star/p/13868172.html