題解
類似於括號匹配,將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;
}