文章目录
A HRZ的序列
1. 题目大意
相较于咕咕东,瑞神是个起早贪黑的好孩子,今天早上瑞神起得很早,刷B站时看到了一个序列a,他对这个序列产生了浓厚的兴趣,他好奇是否存在一个数K,使得一些数加上K,一些数减去K,一些数不变,使得整个序列中所有的数相等,其中对于序列中的每个位置上的数字,至多只能执行一次加运算或减运算或是对该位置不进行任何操作。由于瑞神只会刷B站,所以他把这个问题交给了你!
输入:
输入第一行是一个正整数t表示数据组数。接下来对于每组数据,输入的第一个正整数n表示序列a的长度,随后一行有n个整数,表示序列a
输出:
输出共包含t行,每组数据输出一行。对于每组数据如果存在这样的K,输出“YES”,否则输出“NO”。(输出不包含引号)
样例:
2
5
1 2 3 4 5
5
1 2 3 4 5
NO
NO
2. 思路历程
- 对于满足要求的序列,序列中只能有三个不同的数,
a - k, a, a + k
,因此只对整个序列不同的数字个数进行考虑即可。 - 对序列进行排序,遍历即可计数cnt
- 若
cnt > 3
,则肯定不符合要求 - 若
cnt = 3
,判断三个数是否满足等差数列,若满足则符合要求 - 若
cnt = 1
或cnt = 2
,肯定符合要求
3. 反思
这题我的思路和代码都是正确的,唯独开错了数据范围(补题只改了maxn就通过了),谁能想到我会把104当成四位数呢(唉
一定要注意数据范围啊!
4. 代码
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn = 11111;
int t, n, cnt = 0;
long long a[maxn], now, dif[maxn];
int main()
{
cin >> t;
for (int i = 0; i < t; i++)
{
cin >> n;
for (int j = 0; j < n; j++)
cin >> a[j];
sort(a, a + n);
now = a[0];
dif[0] = a[0];
cnt = 1;
for (int j = 1; j < n; j++)
{
if (a[j] != now)
{
dif[cnt] = a[j];
now = a[j];
cnt++;
}
}
if ((cnt == 3 && dif[1] - dif[0] == dif[2] - dif[1]) || cnt == 2 || cnt == 1)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
return 0;
}
B HRZ学英语
1. 题目大意
瑞神今年大三了,他在寒假学会了英文的26个字母,所以他很兴奋!于是他让他的朋友T考考他,TT想到了一个考瑞神的好问题:给定一个字符串,从里面寻找连续的26个大写字母并输出!但是转念一想,这样太便宜瑞神了,所以他加大了难度:现在给定一个字符串,字符串中包括26个大写字母和特殊字符?,特殊字符?可以代表任何一个大写字母现在T问你是否存在一个位置连续的且由26个大写字母组成的子串,在这个子串中每个字母出现且仅出现一次,如果存在,请输出从左侧算起的第一个出现的符合要求的子串,并且要求,如果有多组解同时符合位置最靠左,则输出字典序最小的那个解!如果不存在,输出-1!这下HRZ蒙圈了,他刚学会26个字母,这对他来说太难了,所以他来求助你,请你帮他解决这个问题,报酬是可以帮你打守望先锋。
说明:字典序先按照第一个字母,以A、B、C的顺序排列;如果第一个字母一样,那么比较第二个、第三个乃至后面的字母。如果比到最后两个单词不一样长(比如,SGH和 SIGHT),那么把短者排在前。例如
AB??EFGHIJKLMNOPQRSTUVWXYZ
ABCDEFGHIJKLMNOPQRSTUVWXYZ
ABDCEFGHIJKLMNOPQRSTUVWXYZ
上面两种填法,都可以构成26个字母,但是我们要求字典序最小,只能取前者。
注意,题目要求的是第一个出现的,字典序最小的!
输入:
输入只有一行,一个符合题目描述的字符串
输出:
输出只有一行,如果存在这样的子串,请输出,否则输出-1
样例:
ABC??FGHIJK???OPQR?TUVWXY?
ABCDEFGHIJKLMNOPQRSTUVWXYZ
2. 思路历程
- 考虑尺取法,左右指针的位置相差25,指针从序列起始位置开始,每次
l++, r++
考虑下一个情况。 - 对于每一段26个字符的序列,计算其中每个字母的个数和问号的个数(最后问号可以换成这部分序列中任何没出现的字母)
- 若未出现的字母数量大于问号的数量,则这一段肯定不符合要求(即使把所有问号换成字母也有字母不存在)
- 若未出现的字母数量小于等于问号的数量,则对这一段的问号进行替换:在遍历序列时就记录问号的位置,之后对于每个位置进行替换
3. 反思
模测时想到了尺取法,但是没有想清楚左右指针的位置和移动方式,当时非常困扰,就先奔去T3了,后来因为时间不够直接没写出来。
感觉自己有点死板于之前讲的尺取法,不知道如何变通,其实这里的左右指针是同时移动的,我按照之前的尺取法考虑还把情况复杂化了。
一定要多思考啊!方法只体现思想,它会有很多不同的实现形式,要根据具体问题具体分析。
CSP题难度严格递增,千万不能因为前一题比较费时间就跳到后一题,这样只会浪费更多时间(唉
4. 代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
using namespace std;
int n, num, cnt;
int l = 0, r = 25, letter[26];
string s, ans;
bool flag;
void solve()
{
memset(letter, 0, sizeof(letter));
cnt = 0; num = 26; ans = "";
queue<int> mark; //记录问号出现的位置
for (int i = l; i <= r; i++)
{
if (s[i] == '?')
{
cnt++;
mark.push(i);
}
else
{
if (!letter[s[i] - 'A'])
num--;
letter[s[i] - 'A']++;
}
ans += s[i];
}
if (num > cnt)
{
flag = false;
return;
}
else
{
for (int i = 0; i < 26; i++)
{
if (!letter[i]) //把问号所在的位置换成letter[i]
{
int pos = mark.front();
mark.pop();
ans[pos - l] = i + 'A';
}
}
cout << ans << endl;
flag = true;
return;
}
}
int main()
{
cin >> s;
long n = s.length() - 1;
while (r <= n)
{
solve();
if (!flag)
{
l++;
r++;
}
else
break;
}
if (!flag)
cout << "-1" << endl;
return 0;
}
C 咕咕东的奇妙序列
1. 题目大意
咕咕东正在上可怕的复变函数,但对于稳拿 Plus的咕咕东来说,她早已不再听课,此时她在睡梦中突然想到了一个奇怪的无限序列:1122312312345这个序列由连续正整数组成的若干部分构成,其中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所有数字,第部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是
1121231234123451234561234567123456781245678912345678910,其中第1项是1,第3项是2,第20项是5,第38项是2,第56项是0。咕咕东现在想知道第k项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。
输入:
输入由多行组成。
第一行一个整数q表示有q组询问(1 <= q <= 500)
接下来第i+1行表示第i个输入k,表示询问第k项数字。(1 <= k <= 1018)
输出:
输出包含q行
第i行输出对询问k的输出结果。
样例:
5
1
3
20
38
56
1
2
5
2
0
2. 思路历程
-
(这道题的细节也太绕了叭)考虑序列中每段的长度(1 ~ n)求和得到整个序列各个段的长度和(1) + (1 ~ 2) + … + (1 ~ n),即
ssum(x) = ∑sum(x)
-
由于 [10i, 10i+1) 的数长度均为 i + 1,所以按照每段最大数的位数划分部分,(如最大数为1 ~ 9属于1位数部分,最大数为10 ~ 99属于2位数部分)从 (1 ~ 10i) … (1 ~ 10i+1 - 1) 的i位数部分内所有
sum(i)
构成一个等差数列,差为i + 1 -
对于给定的x,先求出其所在于哪个位数的部分,再求出其位于该部分中的哪一个数(这里用两次二分法来确定)
- i 位数部分和 = (10i - 1) * 1位数部分和 + (10i - 9)* 2位数部分和 + … + (10i - 10i-1 + 1) * i 位数部分和
- i 位数部分的段的和 = 1 * 9 + 2 * 90 + … + i * (10i - 10i-1)
<–有点像分割求直角三角形的面积–> - 对于x找到最后一个比其小的部分和,再找到其所在的段,最后找到其在段内的哪个数字的哪一位
3. 反思
本来以为可以骗点分,可能要怪自己骗分也太有逻辑(?
我还把序列中的每个部分按照位数进行了讨论,为啥不直接把一部分序列存在代码里面嘞(?
感觉骗分也是个比较重要的技能,既然已经不会了就贯彻到底
4. 代码
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
long long q, k;
long long get_len(long long x)
{
return x * (x + 1) / 2;
}
long long get_sum(long long x)
{
long long ans = 0, part = 1, d = 1;
for (; x >= part * 10; part *= 10, d++)
{
ans += d * get_len(part * 9) + d * part * 9 * (x - part * 10 + 1);
}
return ans + d * get_len(x - part + 1);
}
long long get_num(long long x)
{
long long ans = 0, part = 1, d = 1;
for (; x >= part * 10; part *= 10, d++)
{
ans += d * part * 9;
}
return ans + d * (x - part + 1);
}
int main()
{
cin >> q;
for (int i = 0; i < q; i++)
{
cin >> k;
long long l = 0, r = 1e9, ans1 = 0, ans2 = 0;
while (l <= r)
{
long long mid = (l + r) >> 1;
if (get_sum(mid) < k) // 找到k位于哪一块
{
l = mid + 1;
ans1 = mid;
}
else
r = mid - 1;
}
k -= get_sum(ans1);
l = 0; r = ans1 + 1;
while (l <= r)
{
long long mid = (l + r) >> 1;
if (get_num(mid) < k)
{
l = mid + 1;
ans2 = mid;
}
else
r = mid - 1;
}
k -= get_num(ans2++);
int a[30], j = 0;
memset(a, 0, sizeof(a));
while (ans2)
{
a[j++] = ans2 % 10;
ans2 /= 10;
}
cout << a[j - k] << endl;
}
return 0;
}