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. 由于先向右移动再向左移动,可以笃定二者中一定有一个回文子串,且一定与首或尾相连
  2. 那么就可以暴力枚举回文子串的对称点,判断是否满足 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\)

解析:

  1. \(t\)只有在为\(s\)的子序列的前提下,才有可能满足题目条件。考虑到一个退格键代表消除两个字符,所以这个子序列相邻的字符在字符串\(s\)中必须距离为奇数。如果从前向后考虑,\(s[?]=t[0]\)中这个\(?\)便无法确定(与\(t\)[1]字符满足距离的条件才可),那这样想下去对于后续下标来说就是一个递归的问题了,不太适用于\(O(n)\)的问题
  2. 考虑从后向前,对于最后一个字符,如果更改为退格键,则对应最后一个有效字符的下标就\(-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");
	}
}
posted @ 2021-07-23 19:57  DreamW1ngs  阅读(68)  评论(0编辑  收藏  举报