爬塔(set、括号匹配)
题意
Alice最近迷上了杀怪爬塔的游戏,在塔中有\(n\)层关卡,在通过第\(i\)层关卡后Alice会走上第\(i+1\)层。每个关卡中可能会获得女神的祝福或者遇到怪物。如果得到女神的祝福,Alice生命值会\(+1\),如果遇到怪物生命值会\(-1\)。起初Alice的生命值为\(1\),他想要最后生命值仍然为\(1\)。现在给\(q\)组询问,每组询问给定\(L, R\),问在第\(L\)层到第\(R\)层Alice能通过的关卡数最多是多少?
注意Alice可以从\(L\sim R\)的任意一层出发,期间任何时刻Alice生命值不能为\(0\)。
题目保证在\(1\sim n\)层中,无论Alice从哪一层开始挑战,他能通过的关卡数\(\leq 10\)
数据范围
\(1 \leq n \leq 10^5\)
\(1 \leq q \leq 10^5\)
思路
题目可以转换成遇到\(1\)表示\(+1\),遇到\(0\)表示\(-1\),求一段最长的区间使得区间和为\(0\),且他从起点开始的任一子区间和不能为负数。其实就是经典的最长括号匹配的问题。
因为题目限制了最长只有\(10\),那么直接用set分开保存所有长度为\(x(1\leq x \leq 10)\)的可以匹配的左端点即可。换句话来说,开一个大小为\(10\)的set,然后枚举每个区间\((l, r)\)。如果当前区间和为\(0\),且中间和不会降到负数,那么将\(l\)保存在set[\(r-l+1\)]的左端点集合。
对于每一个区间查询\((l,r)\),从大到小枚举匹配的长度\(x\),如果在对应匹配长度\(x\)的set中能找到\(\geq l\)的下标\(t\)(使用lower_bound即可),并且满足\(t + x - 1 \leq r\),那么\(x\)即为答案。
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;
const int N = 100010;
int n, m;
char s[N];
set<int> st[15];
int main()
{
scanf("%d", &n);
scanf("%s", s + 1);
for(int i = 1; i <= n; i ++) {
int tmp = 1;
for(int j = i; j <= min(n, i + 9); j ++) {
if(s[j] == '1') tmp ++;
else tmp --;
if(tmp <= 0) break;
if(tmp == 1) st[j - i + 1].insert(i);
}
}
scanf("%d", &m);
while(m --) {
int l, r;
scanf("%d%d", &l, &r);
bool flag = false;
for(int i = 10; i >= 1; i --) {
auto t = st[i].lower_bound(l);
if(t == st[i].end()) continue;
if(*t >= l && (*t) + i - 1 <= r) {
printf("%d\n", i);
flag = true;
break;
}
}
if(!flag) printf("0\n");
}
return 0;
}