做题记录 // 230224

GM 说字符串串题是 KMP 的基础,可是 GM 出的串串题和 KMP 有什么关系呢?以及,GM 不是讲过 KMP 吗?哦,好像是三哥讲的。


A. 乒乓球

http://222.180.160.110:1024/contest/3352/problem/1

首先是一个简单的模拟啊。我们直接用 std::string 输入,如果查找到 'E' 就停止。

不过 string::find() 的返回值是一个字符串!很吓人,返回的是待查找内容所在的可见子串。如果找不到就会返回 std::npos,是一个值为 -1 的常量。

但是题目为什么会强调一个胜两颗球才算赢呢?样例没有体现这一点!其实意思就是说,如果碰到了 "WLWLWLWLWLWL..." 这种情况,就要一直不停地比下去,直到分差大于等于 2 为止。

很尴尬!我在 gm 提示之前并没有考虑到输入开头就是 'E' 的情况。所以我们特判一下,如果开头是 'E' 那就输出两个 0:0

又很尴尬!gm 说我是错的。哪里错了呢?我乱举了几个特例发现我没有考虑 ELE 之类的情况,因为我的 'E' 是从后往前判的!神奇!

最后还是错了!错误的原因如下:

  • 所谓 11 分制不是说打了 11 盘就停,是两个人当中有 11 分的才停……
  • 最后一盘不管开始打没有都要输出!所以特判可以删掉了!
namespace XSC062 {
using namespace fastIO;
using str = std::string;
str s, t;
int x, y, cnt;
inline int abs(int x) {
	return x >= 0 ? x : -x;
}
int main() {
#ifdef ONLINE_JUDGE
	freopen("ball.in", "r", stdin);
	freopen("ball.out", "w", stdout);
#endif
	while (std::cin >> t) {
		s += t;
		if (t.find('E') != str::npos)
			break;
	}
	if (s.front() == 'E') {
		printf("0:0\n\n0:0");
		return 0;
	}
	for (int i = 0; i < (int)s.length(); ++i) {
		if (s[i] == 'E') {
			while ((int)s.size() > i)
				s.pop_back();
			break;
		}
	}
	for (auto i : s) {
		(i == 'W') ? ++x : ++y;
		if (++cnt >= 11 && abs(x - y) >= 2) {
			printf("%d:%d\n", x, y);
			x = y = cnt = 0;
		}
	}
	if (cnt != 0) {
		printf("%d:%d\n", x, y);
		x = y = cnt = 0;
	}
	putchar('\n');
	for (auto i : s) {
		(i == 'W') ? ++x : ++y;
		if (++cnt >= 21 && abs(x - y) >= 2) {
			printf("%d:%d\n", x, y);
			x = y = cnt = 0;
		}
	}
	if (cnt != 0) {
		printf("%d:%d\n", x, y);
		x = y = cnt = 0;
	}
	return 0;
}
} // namespace XSC062

B. 复制粘贴 2

http://222.180.160.110:1024/contest/3352/problem/2

为什么每次碰到这道题我都不会呢?因为是 lhy 讲的 因为是班长说的 因为每次我都觉得这是一道字符串串 / 数据结构题,没想到他居然是思维!

总而言之,一个简单的点是,我们其实不用存储每个位置具体的值,只用根据操作去推某个位置的值在原字符串的哪个位置就可以了(因为操作都是复制粘贴嘛,所以不会出现新的字符)。

我们枚举 \(1\sim k\) 中的每个位置,倒序遍历操作。假设当前位置为 \(i\),以 \(p\) 记录最终点位。一开始 p = i,如果在 \(p\) 之前有字符串插入,我们就分讨一下:

  • 假如插入字符串后新字符串覆盖了位置 \(p\),那么位置 \(p\) 的值就应该在被插入的字符串中;将 \(p\) 更新为插入字符串中的对应位置。
  • 否则,\(p\) 往前挪几位就可以了。

一定要注意题目中对「位置」的定义呀!我用了闭区间的写法然后上天了!

namespace XSC062 {
using namespace fastIO;
const int maxn = 2e5 + 5;
int k, m, n, p;
char s[maxn], t[maxn];
int l[maxn], r[maxn], w[maxn];
int main() {
	read(k), read(m);
	scanf("%s", s + 1);
	read(n);
	for (int i = 1; i <= n; ++i) {
		read(l[i]), read(r[i]);
		read(w[i]), ++l[i];
	}
	for (int i = 1; i <= k; ++i) {
		p = i;
//		printf("i = %d:\n  p = %d\n", i, p);
		for (int j = n; j; --j) {
			if (w[j] < p && w[j] + r[j] - l[j] + 1 >= p)
				p = l[j] + p - w[j] - 1;
			else if (w[j] < p)
				p -= r[j] - l[j] + 1;
//			printf("  p = %d\n", p);
		}
		putchar(s[p]);
	}
	return 0;
}
} // namespace XSC062

C. 愉快的标志设计

http://222.180.160.110:1024/contest/3352/problem/3

哈哈哈,円状 JOI,只能说霓虹文化也挺博大精深!

GM 说要前缀和,但是我不会!摆!

好吧,其实很简单,因为这个类似于分形的构造,在记录当前前缀和后,只需要 \(\log_4 n\) 就可以求得一个长度为 \(n\) 的字符串所需改变的字符数量……

然后我们一个深搜就可以解决了!

namespace XSC062 {
using namespace fastIO;
const int inf = 0x3f3f3f3f;
const int maxn = (1 << 20) + 5;
char s[maxn << 2];
int n, k, res = inf;
int cj[maxn << 2], co[maxn << 2], ci[maxn << 2];
inline int min(int x, int y) {
	return x < y ? x : y; 
}
int DFS(int l, int r, int k) {
	if (l == r)
		return 0;
	int res = k - (cj[l + k - 1] - cj[l - 1])
			+ k - (co[l + 2 * k - 1] - co[l + k - 1])
			+ k - (ci[l + 3 * k - 1] - ci[l + 2 * k - 1]);
	return res + DFS(l + 3 * k, r, k / 4);
}
int main() {
	scanf("%d %s", &k, s + 1);
	n = (1 << (k * 2));
	for (int i = 1; i <= n; ++i) {
		cj[i] = cj[i - 1] + (s[i] == 'J');
		co[i] = co[i - 1] + (s[i] == 'O');
		ci[i] = ci[i - 1] + (s[i] == 'I');
	}
	for (int i = 1; i <= n; ++i) {
		s[i + n] = s[i];
		cj[i + n] = cj[i + n - 1] + (s[i + n] == 'J');
		co[i + n] = co[i + n - 1] + (s[i + n] == 'O');
		ci[i + n] = ci[i + n - 1] + (s[i + n] == 'I');
		res = min(res, DFS(i, i + n - 1, n / 4));
	}
	print(res);
	return 0;
}
} // namespace XSC062
int main() {
	XSC062::main();
	return 0;
}

D. JOIOJI

http://222.180.160.110:1024/contest/3352/problem/4

不难想到用相对关系来记录!也是一个类似于前缀和的思想!

我们记录一下前缀和,假设当前遍历到 \(i\)'O' 的数量减去 'J' 的数量是 \(x\)'I' 的数量减去 'J' 的数量是 \(y\)

那么我们用一个 map 来存储这个信息。在 \(x\) 这个 map 里记录 \(y\) 这个数量和 \(i\) 这个位置。

我们在 \(x\) 这个 map 里找到 \(y\),然后下标一相减,三个字母不就平上了?

这里有个小细节,如果在插入之前已经有 \(y\) 这个元素不需要更新!因为我们需要串串最长,转化为下标最小。

以及如果你是在 at 上交的需要输出文末回车才可以!

namespace XSC062 {
#define mkp std::make_pair
const int maxn = 2e5 + 5;
using pii = std::pair<int, int>;
char s[maxn];
int n, cj, co, ci, p, t, res;
std::vector<pii> g[maxn << 1];
inline int max(int x, int y) {
	return x > y ? x : y;
}
int main() {
	scanf("%d %s", &n, s + 1);
	for (int i = 1; i <= n; ++i) {
		if (s[i] == 'J')
			++cj;
		else if (s[i] == 'O')
			++co;
		else ++ci;
		t = co - cj + n;
		p = std::lower_bound(g[t].begin(),
			g[t].end(), mkp(ci - cj, i)) - g[t].begin();
		if (p >= (int)g[t].size())
			g[t].push_back(mkp(ci - cj, i));
		else res = max(res, i - g[t][p].second + 1);
	}
	printf("%d", res);
	return 0;
}
} // namespace XSC062

E. 统计单词数

http://222.180.160.110:1024/contest/3352/problem/5

很简单的模拟!GM 说这个和明天要讲的暴力匹配很像!到底哪里像了?

还是挂了!因为我只判了单词后面是不是空格,但是没有判单词前面有没有!我是聪明!

namespace XSC062 {
using namespace fastIO;
using str = std::string;
str t, s;
int cnt, res = -1;
inline char f(char ch) {
	if ('A' <= ch && ch <= 'Z')
		return ch - 'A' + 'a';
	return ch;
}
int main() {
#ifdef ONLINE_JUDGE
	freopen("stat.in", "r", stdin);
	freopen("stat.out", "w", stdout);
#endif
	std::getline(std::cin, t, '\n');
	std::getline(std::cin, s, '\n');
	for (int i = 0; i < (int)s.size(); ++i) {
		if (s[i] == ' ')
			continue;
		for (int j = 0; j < (int)t.size(); ++j) {
			if (f(t[j]) != f(s[i + j]))
				goto NoSol;
		}
		if (s[i + t.size()] != ' '
				|| (i != 0 && s[i - 1] != ' '))
			goto NoSol;
		++cnt;
		if (res == -1)
			res = i;
		NoSol: ;
	}
	if (cnt == 0)
		puts("-1");
	else print(cnt, ' '), print(res);
	return 0;
}
} // namespace XSC062

为什么是字符串串而不是字符串呢?因为是班长说的!

为什么是班长说的而不是别人说的呢?因为是班长说的!

posted @ 2023-02-24 19:58  XSC062  阅读(31)  评论(0编辑  收藏  举报