一、总体情况
- 考试一共有五道题
- 这次考试考得非常差,一直在死磕第二道题,但最后没做出来
二、题解
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 k≤n,则返回对应的字母即可。
代码
#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 i−1 就是答案。
代码
#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 题,本来一个循环就可以解决的,我考试时非要写递归……
四、下次考试时的改进措施
- 给每道题先规划好做题的时间,如果超出了这个时间还没做对或还没有思路,就直接跳过,先看下一题;
- 对于有思路的题,写前先预估一下那种写法不容易错,不要盲目开写。