小P的秘籍 ZZULIOJ - 1523 二分 ST表

题解

类似于括号匹配,将K看为1将A看为-1做前缀和记为a。如果某个区间[l, r]合法则a[l - 1]为当前区间内的a的最小值、a[r]为区间内的a最大值。
使用单调栈求出两个数组l、r,l表示当前位置向左第一个大于当前a的位置、r表示当前位置向右第一个小于当前a的位置。
枚举答案区间的左端点i二分右端点,二分范围为[i, r[i] - 1],右端点如果为r[i]则非法而小于这个值对于从左向右来说都是合法的。
使用ST表维护l数组的区间最小值,每次二分的时候检测[m + 1, R]是否有满足条件的右端点,如果有则放弃左区间向右寻找。
最后二分到一个点的时候停止,当前点为最右的可行右端点计算答案。

AC代码

#include <stdio.h>
#include <bits/stdc++.h>
#define fst first
#define sed second
using namespace std;
typedef long long ll;

const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
const int N = 1e6 + 10;
char s[N];
int a[N], l[N], r[N]; //l向左第一个大于i的位置 r向右第一个小于i的位置
int lg2[N], st[20][N];

void GetST(int n)
{
	lg2[0] = -1;
	for (int i = 1; i < N; ++i)
		lg2[i] = lg2[i >> 1] + 1;
	for (int i = 1; i <= n; ++i)
		st[0][i] = l[i];
	for (int i = 1; i <= lg2[n]; ++i)
		for (int j = 1; j + (1 << i) - 1 <= n; ++j)
			st[i][j] = min(st[i - 1][j], st[i - 1][j + (1 << i - 1)]);
}
int Ask(int l, int r)
{
	int w = lg2[r - l + 1];
	return min(st[w][l], st[w][r - (1 << w) + 1]);
}
int main()
{
#ifdef LOCAL
	freopen("C:/input.txt", "r", stdin);
#endif
	int n;
	cin >> n;
	scanf("%s", s + 1);
	for (int i = 1; i <= n; ++i)
		a[i] = a[i - 1] + 1 - 2 * (s[i] == 'A');
	stack<int> stk; //位置
	stk.push(0); //r需要计算0位置
	for (int i = 1; i <= n; ++i)
	{
		while (!stk.empty() && a[i] < a[stk.top()])
			r[stk.top()] = i, stk.pop();
		stk.push(i);
	}
	while (!stk.empty())
		r[stk.top()] = n + 1, stk.pop();
	for (int i = n; i >= 1; --i)
	{
		while (!stk.empty() && a[i] > a[stk.top()])
			l[stk.top()] = i, stk.pop();
		stk.push(i);
	}
	while (!stk.empty())
		l[stk.top()] = 0, stk.pop();
	GetST(n); //st表维护l数组区间最小值 表示范围内是否有满足条件的右端点
	int ans = 0;
	for (int i = 1; i <= n; ++i) //枚举左端点 a[l-1]为区间最小值a[r]为区间最大值则满足条件
	{
		int L = i, R = r[i - 1] - 1; //二分右端点 找到最靠右的满足条件的右端点
		while (L < R)
		{
			int M = L + R >> 1;
			int res = Ask(M + 1, R);
			if (res < i) //右侧能找到位置p且[i, p]内a[p]为最大值 满足条件
				L = M + 1; //抛弃左侧 尽量靠右
			else
				R = M; //右侧没有满足的直接抛弃
		}
		ans = max(ans, R - i + 1); //最终为最右右端点 用R
	}
	cout << ans << endl;

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值