P3514题解

给一个只有1和2的序列,每次询问有没有一个子串的和为x

SPJ对于格式的要求较为严格。对于每个询问后,应紧跟一个换行符。在最后一次输出你的答案以及一个换行符后不应有任何输出。

由zhouyonglong提供SPJ

题解

首先因为元素只能为1/2,所以我们可以思考如何利用上这个性质,可以发现如下重要性质

若存在一个和为\(k\)的区间,则一定有一个和为\(k-2\)的区间

证明:设这个区间为\([l,r]\),则对\(l,r\)两个位置上的权值分类讨论,若二者其中至少有一个权值为2,那么就把那个端点删去就得到了一个和为\(k-2\)的区间,而若二者的权值都为1,从两端删去即可

那么我们就可以考虑将一个足够大的区间往回缩,不断递归,可以得到与此区间的和的奇偶性相同且小于的所有值所对区间

到了这里,做法就很显然了,先找到最大奇数段和最大偶数段,然后递归预处理每个值的答案区间即可,复杂度只有\(O(N)\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 2000050
#define scanf scanf_s
struct node {
	int l, r;
}ans[N];
int val[N], n, m, q, s1[N], s2[N], pos1, pos2;
void dfs(int l, int r, int k) {
	if (ans[k].l)return;
	if (l > r)return;
	ans[k] = { l,r };
	if (val[l] == 2) {
		dfs(l + 1, r, k - 2);
	}
	else if (val[r] == 2) {
		dfs(l, r - 1, k - 2);
	}
	else dfs(l + 1, r - 1, k - 2);
}
void init() {
	memset(ans, 0, sizeof ans);
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		char x;
		cin >> x;
		if (x == 'T')val[i] = 2;
		else val[i] = 1;
	}
	for (int i = 1; i <= n; i++)s1[i] = s1[i - 1] + val[i];
	for (int i = n; i >= 1; --i)s2[i] = s2[i + 1] + val[i];
	for (int i = 1; i <= n; i++)if (val[i] == 1) { pos1 = i + 1; break; }
	for (int i = n; i > 0; --i)if (val[i] == 1) { pos2 = i - 1; break; }
	if (s2[pos1] > s1[pos2]) { dfs(pos1, n, s2[pos1]); }
	else { dfs(1, pos2, s1[pos2]); }
	dfs(1, n, s1[n]);
}
int main() {
	init();
	while (m--) {
		int k;
		scanf("%d", &k);
		if (ans[k].l == 0)puts("NIE");
		else printf("%d %d\n", ans[k].l, ans[k].r);
	}
	return 0;
}
posted @ 2022-11-30 22:54  spdarkle  阅读(21)  评论(0编辑  收藏  举报