Harbour.Space Scholarship Contest 2021-2022 (open for everyone, rated, Div. 1 + Div. 2)
A(水题)
题目链接
⭐
题目:
定义一个数\(x\)若为 interesting 则\(x\)各数位加起来的和大于\(x+1\)各数位加起来的和,现给出\(n\),问\(1\sim n\)之间有多少个 interesting 的数
解析:
只有个位数为9的时候才会满足题意,那也就是求得有多少个以9结尾的数字即可
#include<bits/stdc++.h>
using namespace std;
int main() {
int T, n;
scanf("%d",&T);
while (T--) {
scanf("%d", &n);
printf("%d\n", n / 10 + (n % 10 == 9));
}
}
B(暴力+KMP)
题目链接
⭐⭐
题目:
给出一个字符串\(s\),可以从任意下标开始,先向右移动(可以不动)再向左移动(可以不动),问是否存在移动过程中经过的字符轨迹为字符串\(t\)
解析:
- 由于先向右移动再向左移动,可以笃定二者中一定有一个回文子串,且一定与首或尾相连
- 那么就可以暴力枚举回文子串的对称点,判断是否满足 1 中所叙述的条件,并判断较长部分的串是否存在于\(s\)串中即可
C(水题)
题目链接
⭐
题目:
给出一个点球大战的结果,0 代表未进, 1 代表进球, ? 代表未知,问判断一方获得胜利至少要踢多少次点球
解析:
以判断A方胜利为例,则理想情况是当前第\(x\)轮踢球后,小于\(x\)的A方未知(?)的进球全为命中,而B方全不命中的情况下,即使A方后续一个球也踢不进,B方全部命中,也无法追平比分,那也就是暴力判断一下\(1\sim 10\)的轮数是否可以结束比赛
#include<bits/stdc++.h>
using namespace std;
char s[15];
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%s", s + 1);
int t[2] = { 0 };
int c[2] = { 0 };
int i;
for (i = 1; s[i]; ++i) {
if (s[i] == '?') ++t[i & 1];
else if (s[i] == '1') ++c[i & 1];
if (c[0] + t[0] > (10 - i) / 2 + c[1] || c[1] + t[1] > (11 - i) / 2 + c[0]) break;
}
printf("%d\n", min(i, 10));
}
}
D(思维)
题目链接
⭐⭐
题目:
现在给出一个字符串\(s\),问是否可以将串中键入某字符的操作更改为使用 Backspace(退格键),使得字符串变为\(t\)
解析:
- \(t\)只有在为\(s\)的子序列的前提下,才有可能满足题目条件。考虑到一个退格键代表消除两个字符,所以这个子序列相邻的字符在字符串\(s\)中必须距离为奇数。如果从前向后考虑,\(s[?]=t[0]\)中这个\(?\)便无法确定(与\(t\)[1]字符满足距离的条件才可),那这样想下去对于后续下标来说就是一个递归的问题了,不太适用于\(O(n)\)的问题
- 考虑从后向前,对于最后一个字符,如果更改为退格键,则对应最后一个有效字符的下标就\(-2\),而\(s\)中最后一个没有被替换的字符也是\(t\)中最后一个字符,那也就可以看出,这样可以唯一确定\(t\)中最后一个字符的下标在原字符串\(s\)中的奇偶性,继续递推,剩余的字符都可以被唯一确定
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
char s[maxn], t[maxn];
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%s%s", s, t);
int i = strlen(s) - 1, j = strlen(t) - 1;
while (j >= 0 && i >= 0) {
if (s[i] == t[j]) --j, --i;
else i -= 2;
}
printf("%s\n", j >= 0 ? "NO" : "YES");
}
}
E(思维+排列)
题目链接
⭐⭐⭐
题目:
对\([1,2,3,\dots]\)的序列,可以对他进行两种操作
- 将序列循环右移\(k\)位
- 进行不超过\(m\)次的数值交换
现在给出\(n,m,\)结果序列,问有多少个满足条件的\(k\)可以通过数值交换得到结果序列,并增序输出\(k\)
解析:
引理: 对于一个长度位\(n\)排列\(a\)和排列\(b\),让\(a=b\)的最小交换次数是\(n\)与将\(a[i]\)与\(b[i]\)连接后,数组长度与对应的图中环数的差
证明: 对于无向图中任意一个环,假如环中点的数量为\(c\),则当\(c-1\)个数位置都正确以后,剩下的数位置也正确,而且环内每次交换操作(除了最后一次)至多可以使得1个数回归原位,故每个环都可以少进行1次交换
考虑到\(m\)次数值交换,最多使得\(2m\)个数与原数列位置不同(每次选择未选过的两个数),那考虑记录结果数列\(p\)中每个数,对 "相对于\([1,2,3,\dots]\)的偏移量" 所作出的贡献,那如果这个数\(cnt+2*m\ge n\),那就是有可能在交换次数小于等于\(m\)时实现的,这样的粗略判定的基础上再用引理进行\(O(n)\)的准确破案顶。另外通过\(m\)的范围可以计算出,能通过粗略判定的数不超过3个
#include<bits/stdc++.h>
using namespace std;
const int maxn = 3e5 + 5;
int p[maxn];
int cnt[maxn];
bool vis[maxn];
vector<int> v;
int n, m;
bool check(int k) {
v.clear();
for (int i = k; i < n; ++i)
v.push_back(p[i]);
for (int i = 0; i < k; ++i)
v.push_back(p[i]);
int t = n;
memset(vis, 0, sizeof(vis));
for (int i = 0; i < n; ++i) {
if (vis[i]) continue;
int j = i;
while (!vis[j])
vis[j] = true, j = v[j];
--t;
}
return t <= m;
}
int main() {
int T;
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &m);
for (int i = 0; i < n; ++i) {
scanf("%d", p + i);
--p[i];
++cnt[(i - p[i] + n) % n];
}
vector<int> ans;
for (int i = 0; i < n; ++i)
if (cnt[i] + 2 * m >= n && check(i))
ans.push_back(i);
printf("%d ", ans.size());
for (auto& i : ans)
printf("%d ", i);
printf("\n");
}
}