Bootstrap

07/03/24 融合热身赛赛后总结&题解

一、总体情况

  • 考试一共有五道题
  • 这次考试考得非常差,一直在死磕第二道题,但最后没做出来

二、题解

A题 复制文章

题目描述

一天晚上,马克意识到明天要交一篇文章。他还没有写任何东西,所以马克决定随机从提示中复制粘贴子字符串来制作这篇文章。

更正式地说,提示是一个初始长度为𝑛的字符串。马克将执行𝑐次复制粘贴操作。每个操作由两个整数𝑙和𝑟描述,这意味着马克将字母 s l , s l + 1 + 1... s r s_{l}, s_{l + 1}+1...s_{r} sl,sl+1+1...sr附加到字符串𝑠的末尾。请注意,此操作后𝑠的长度增加。

当然,马克需要能够看到已经写了什么。复制后,马克将询问 q q q 个查询:给定一个整数 k k k,确定最终字符串 s s s 的第 k k k 个字母。

思路分析

这道题的数据给的都特别大,如果按照正常做法每次模拟马克的操作的话,肯定会TLE+MLE。所以,这道题要想一些其它的思路,假设现在要查询第 k k k 个字母,则我们可以先算出 k k k 是在哪次操作之后被加入的,再通过那次操作的左右端点,进一步缩小 k k k 的范围,直到 k ≤ n k ≤ n kn,则返回对应的字母即可。

代码
#include <string>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 45;

int t;
int n, c, q;
int k;
string s;
int sum[N];
int l[N], r[N];

char query(int pos) {
	while (true) {
		if (pos <= n)
			return s[pos];
		int tmp = lower_bound(sum, sum + c + 1, pos) - sum;
		pos = r[tmp] - (sum[tmp] - pos);
	}
}

int main() {
	cin >> t;
	while (t-- > 0) {
		memset(sum, 0, sizeof(sum));
		memset(l, 0, sizeof(l));
		memset(r, 0, sizeof(r));
		cin >> n >> c >> q;
		cin >> s;
		sum[0] = s.size();
		l[0] = 1;
		r[0] = s.size();
		s = " " + s;
		for (int i = 1; i <= c; i++ ) {
			cin >> l[i] >> r[i];
			sum[i] = sum[i - 1] + r[i] - l[i] + 1;
		}
		for (int i = 1; i <= q; i++) {
			cin >> k;
			cout << query(k) << '\n';
		}
	}
	return 0;
}

B题 Excel坐标

题目描述

在流行的电子表格系统中(例如,在Excel中),使用以下列编号。第一列有数字A,第二列-数字B等,直到第26列用Z标记。然后是两个字母数字:第27列有编号AA,28-AB,第52列用AZ标记。在ZZ之后是三个字母数字等。

行用以1开头的整数表示。单元格名称是列和行号的连接。例如,BC23是第55列第23行的单元格的名称。

有时使用另一种数字系统:RXCY,其中X和Y是整数,恭敬地显示列号和行号。例如,R23C55是上一个例子中的单元格。

你的任务是编写一个程序,读取给定的单元格坐标序列,并生成根据另一个数字系统的规则编写的每个项目。

思路分析

这道题其实就是一道模拟题,只不过多了一个10进制和26进制的互相转化,但在转化的时候要注意:26进制是从 A A A Z Z Z,没有 0 0 0,这点需要特别注意。

代码
#include <iostream>
#include <sstream>
#include <stack>

using namespace std;

int n;
string s;

bool isDigit(char c)
{
    if (c >= '0' && c <= '9')
        return true;
    return false;
}

string change(int num)
{
    string res = "";
    stack<int> tmp;
    while (num > 0) {
        tmp.push(num % 26);
        if (num % 26 == 0)
            num /= 26, num--;
        else
            num /= 26;
    }
    while (!tmp.empty()) {
        if (tmp.top() != 0)
            res += tmp.top() - 1 + 'A';
        else
            res += 'Z';
        tmp.pop();
    }
    return res;
}

void culc(int idx, string& ans)
{
    ans = change(idx);
}

void culc(string a, string b, string& ans)
{
    int y = 0;
    for (int i = b.size() - 1, j = 1; i >= 0; i--, j *= 26)
        y += (b[i] - 'A' + 1) * j;
    ans = "R";
    ans += a;
    ans += "C";
    ans += to_string(y);
}

void func(string& s)
{
    bool flag = false; // 标记是否是R_C_型的坐标
    int x = 0, y = 0;
    int t1 = 0, t2 = 0;
    for (int i = 0; i < s.size(); i++)
        if (s[i] == 'C') {
            t2 = i;
            break;
        }
    for (int i = t1 + 1; i < t2; i++)
        if (isDigit(s[i]))
            flag = true;
    if (s[0] != 'R')
        flag = false;
    if (flag) {
        int i = 0;
        string tmp = "";
        for (i = 0; i < s.size() && s[i] != 'C'; i++)
            if (isDigit(s[i]))
                tmp += s[i];
        for (; i < s.size(); i++)
            if (isDigit(s[i]))
                y = y * 10 + (s[i] - '0');
        s.clear();
        culc(y, s);
        s += tmp;
    } else {
        string tmp = "";
        string tmp2 = "";
        for (int i = 0; i < s.size(); i++) {
            if (isDigit(s[i]))
                tmp += s[i]; // 存数字,直接用
            else
                tmp2 += s[i]; // 存字母
        }
        s.clear();
        culc(tmp, tmp2, s);
    }
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++) {
        s.clear();
        cin >> s;
        func(s);
        cout << s << '\n';
    }
    return 0;
}

C题 洗碗

题目描述

Bessie 和 Elsie 正在帮助 Farmer John 洗碗,这是一个比人们想象的更复杂的过程。

两头奶牛决定 Bessie 负责涂肥皂,Elsie 负责冲洗。

刚开始的时候, N N N 个脏盘子(保证是从 1 1 1 N N N 的一个排列)堆在 Bessie 那里,而 Elsie 这边的堆是空的。而在她们俩之间,则有一张专门放涂过肥皂的盘子的桌子。

每个冲洗步骤需要执行以下两个操作之一:

  • Bessie 从脏盘子堆顶取出一个盘子,涂上肥皂,然后放在桌子上。将这个盘子放在桌子上时,Bessie 只能放在现有的非空盘堆的顶端,或是在最右边新增一个盘堆。
  • Elsie 从桌子最左边的盘堆的顶端拿起盘子,将它冲洗后放在干净的盘堆顶端。

她们希望干净的盘堆能按编号排序,编号最小的在底端,编号最大的在顶端。然而她们发现有的时候这并不可能做到。现在给定脏盘子的堆叠顺序,请你求出一个最大前缀,使得该前缀的所有盘子洗干净后,能按上面的要求堆叠。

思路分析

这道题只要把题目读懂了,其实就非常简单。这道题题目的意思其实是说:给你 n n n 堆盘子,每个盘子都有一个编号,每次拿到一个盘子时,可以选择在右边另起一堆,或者就放在这一堆上面。在这些操作之间,也可以选择从左边堆的顶上开始拿走一些盘子。而最后要使拿走的盘子尽量降序,让你求出降序的数量。

剩下的直接用双端队列模拟即可。其中,当一个盘子的编号大于当前堆的所有盘子的编号时,就另起一堆放这个盘子。否则,就有二分找到这个盘子应该放在哪个栈里面,然后把小于这个盘子的全部弹出,并记录弹出的编号的最大值。若之后又一个盘子 a i a_{i} ai 的编号 < < < 这个值时, i − 1 i - 1 i1 就是答案。

代码
#include <iostream>
#include <queue>

using namespace std;

const int N = 1e5 + 5;

int n;
int a[N];
int idx = 0;
int tmp = 0;
deque<int> v[N];

int main()
{
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	int now;
	for (int i = 1; i <= n; i++) {
		now = a[i];
		if (idx == 0) {
			v[++idx].push_front(now);
		} else {
			if (now < tmp) {
				cout << i - 1 << '\n';
				return 0;
			}
			if (now > v[idx].back()) {
				v[++idx].push_back(now);
				continue;
			}
			int l = 1, r = idx;
			int m = l + r >> 1;
			while (l < r) {
				m = l + r >> 1;
				if (v[m].back() >= now)
					r = m;
				else
					l = m + 1;
			}
			while (!v[l].empty() && v[l].front() < now) {
				tmp = max(tmp, v[l].front());
				v[l].pop_front();
			}
			v[l].push_front(now);
		}
	}
	cout << n << '\n';
	return 0;
}
*/

D题 铺设道路

题目描述

春春是一名道路工程师,负责铺设一条长度为 n n n 的道路。

铺设道路的主要工作是填平下陷的地表。整段道路可以看作是 n n n 块首尾相连的区域,一开始,第 i i i 块区域下陷的深度为 d i d_i di

春春每天可以选择一段连续区间 [ L , R ] [L,R] [L,R] ,填充这段区间中的每块区域,让其下陷深度减少 1 1 1。在选择区间时,需要保证,区间内的每块区域在填充前下陷深度均不为 0 0 0

春春希望你能帮他设计一种方案,可以在最短的时间内将整段道路的下陷深度都变为 0 0 0

思路分析

这道题其实就是一个用差分来做区间修改的模版题,要把整个区间变为 0 0 0,其实就是把差分数组中的每一项都变为 0 0 0,只需要求出差分数组后,将数组中正数和和负数和的绝对值取max,就是这个问题的答案。因为较小的部分可以两两抵消,剩余的部分(即两个和的差的绝对值)只能单独抵消。

代码
#include <iostream>

using namespace std;

const int N = 1e5 + 5;

int n;
int a[N];
int d[N];

void modify(int l, int r, int c)
{
    d[l] += c;
    d[r + 1] -= c;
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        modify(i, i, a[i]);
    }
    long long sum_positive = 0LL;
    long long sum_negative = 0LL;
    for (int i = 1; i <= n; i++)
        if (d[i] >= 0)
            sum_positive += d[i];
        else
            sum_negative += d[i];
    cout << max(abs(sum_negative), abs(sum_positive)) << '\n';
    return 0;
}

E题 货币系统

题目描述

在网友的国度中共有 n n n 种不同面额的货币,第 i i i 种货币的面额为 a [ i ] a[i] a[i],你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为 n n n、面额数组为 a [ 1.. n ] a[1..n] a[1..n] 的货币系统记作 ( n , a ) (n,a) (n,a)

在一个完善的货币系统中,每一个非负整数的金额 x x x 都应该可以被表示出,即对每一个非负整数 x x x,都存在 n n n 个非负整数 t [ i ] t[i] t[i] 满足 a [ i ] × t [ i ] a[i] \times t[i] a[i]×t[i] 的和为 x x x。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额 x x x 不能被该货币系统表示出。例如在货币系统 n = 3 n=3 n=3, a = [ 2 , 5 , 9 ] a=[2,5,9] a=[2,5,9] 中,金额 1 , 3 1,3 1,3 就无法被表示出来。

两个货币系统 ( n , a ) (n,a) (n,a) ( m , b ) (m,b) (m,b) 是等价的,当且仅当对于任意非负整数 x x x,它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统 ( m , b ) (m,b) (m,b),满足 ( m , b ) (m,b) (m,b) 与原来的货币系统 ( n , a ) (n,a) (n,a) 等价,且 m m m 尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的 m m m

思路分析

这道题很容易就能想到:只要可以用若干种别的货币凑出其中的一种,那这一种货币就可以被优化掉。所以,这道题就变成了找出系统中有多少货币满足以上条件,可以考虑DP求解。

定义状态 D P [ i ] DP[i] DP[i] i i i 表示 i i i 这种金额, D P [ i ] DP[i] DP[i] 若等于 1 1 1,则表示这种金额时本来就存在的, D P [ i ] DP[i] DP[i] 若等于 2 2 2,则表示这种金额可以被凑出来。

状态转移也非常简单,若 D P [ i ] ≥ 0 DP[i] ≥ 0 DP[i]0,则可以枚举一遍所有货币,则 i i i 加上这些货币的值的 D P DP DP 值肯定 ≥ 0 ≥ 0 0。并且,在这个过程中就可以统计出答案。

代码
#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 105;

int t;
int n;
int a[N];
int dp[25005];

int main() {
	cin >> t;
	while (t-- > 0) {
		memset(dp, 0, sizeof(dp));
		memset(a, 0, sizeof(a));
		cin >> n;
		for (int i = 1; i <= n; i++)
			cin >> a[i];
		sort(a + 1, a + n + 1);
		for (int i = 1; i <= n; i++)
			dp[a[i]] = 1;
		int ans = 0;
		for (int i = 1; i <= a[n]; i++)
			if (dp[i] != 0)
				for (int j = 1; j <= n; j++)
					if (i + a[j] <= a[n]) {
						if (dp[i + a[j]] == 1)
							ans++;
						dp[i + a[j]] = 2;
					}
		cout << n - ans << '\n';
	}
	return 0;
}

三、存在的不足

这次考试的不足是显而易见的,整整三个小时,花了两个半小时左右在调 B B B 题,后面的题看都没看一眼, A A A 题在考试的时候想出了正解,但代码一直写不对。

总的来说,这侧考试总共有以下几点不足:

  • 虽然说这次考试的时候没有像上次那样慌张,但有的时候我做题太倔强了,一道题我觉得自己会做,就一定会死磕到底,不AC就不看下一道,导致我错过了 D D D E E E 两道简单题;
  • 对于已经想出了思路的题来说,我经常直接就开写,也不想一想怎么写最不容易错,就像这次的 A A A 题,本来一个循环就可以解决的,我考试时非要写递归……

四、下次考试时的改进措施

  • 给每道题先规划好做题的时间,如果超出了这个时间还没做对或还没有思路,就直接跳过,先看下一题;
  • 对于有思路的题,写前先预估一下那种写法不容易错,不要盲目开写。

五、谢谢大家阅读

;