这道题让我在2.29号的上午自闭了数个小时。。。
首先分享一个非常不好的习惯--做题看标签:
于是可怜的我就被坑了。。。
看到二分答案,我首先打了个二分,想check函数。
问题转化为下面:我们把每个(p)想象成(1),把每个(j)想象成(-1),验证是否存在长度为(mid)的序列满足文中条件。
对于该序列[i,j],我们可以维护一个前缀和(s1)和一个后缀和(s2),那么我们只需要在[i,j]找到一个最小的(s1[k]),检查(s1[k] - s1[i-1])是否非负,找到一个(s2[w]),检查(s2[w]-s2[j+1])是否非负。
对于这两个最小值我们完全可以用双端队列来维护。
然后蒟蒻愉快地打好了代码。
WA穿,当场去世
仔细想一想,其实这道题它并不存在单调性,因为长度为(x+y)的序列存在,但长度为(x)的却不一定存在,因为这里要考虑正着取和倒着取。
随便举个例子都能发现
所以大家千万不要无脑二分,二分前一定要检查单调性。。。
意识到这一点的我高度自闭,但显然,我的check函数中所用到的思路正解必然也会用到
不然我为什么要讲。
我们现在的已知有用条件就是对于一个序列判断他是否合法,即最小的(s1[k])必须大于等于(s1[i-1]),最小的(s2[w])必须大于等于(s2[j+1]),这两个条件从本质上来说是一样的,所以我们先选取第一个为根本进行分析。
既然(s1[k])必须大于等于(s1[i-1]),那么我们何不先把(i)确定下来,然后寻找以(i)为左端点的最长合法区间长度呢?我们把(i)确定下来后,我们又可以找到一个最小的(j1),使得(s1[j1])小于(s1[i-1])。这就意味着,我们最长合法区间的长度的(j)必然小于(j1)。
我们现在要根据第二个条件确定最终的区间长度。怎么办呢?既然最小的(s2[w])必须大于等于(s2[j+1]),那么(s2[j+1])必然小于该区间的每一个数,所以(s2[j+1])必然是区间([i+1,j1])内的最小值,如果更小则不一定最长,如果更长,那么另外的(s2[j+1])就必然大于(s2[w])。
讲一下实现:
我们现在要枚举每一个(i),然后找到在他右边的第一个小于它的位置。
关于此,我们可以用线段树处理,使用线段树维护前缀和的区间最小值,如果左边的最小值比(i)的值小,则往左边走,否则往右边走,每预处理一个(i)就把他的值改为极大值。(特别鸣谢(LYC)巨佬的帮助)
现在,我们要查询区间最小,显然是线段树维护后缀和的区间最小对吧。
这道题代码实现起来还是很烦的,希望大家自己实现一下。
实在不行再来参考一下我的结构。
#include <deque>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 1e6 + 5;
int s1[MAXN] , n , ans[MAXN];
char s[MAXN];
struct node {
int id , val;
}t[MAXN << 2];
void build(int l , int r , int now) {
if(l == r) {
t[now].val = s1[l];
t[now].id = l;
return;
}
int mid = (l + r) >> 1;
build(l , mid , now << 1);
build(mid + 1 , r , now << 1 | 1);
if(t[now << 1].val < t[now << 1 | 1].val)
t[now] = t[now << 1];
else t[now] = t[now << 1 | 1];
}
int find(int l , int r , int now , int x) {
if(l == r) return l;
int mid = (l + r) >> 1;
if(t[now << 1].val < x) return find(l , mid , now << 1 , x);
return find(mid + 1 , r , now << 1 | 1 , x);
}
void update(int l , int r , int now , int x) {
if(l == r) {
t[now].val = 1e9;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) update(l , mid , now << 1 , x);
else update(mid + 1 , r , now << 1 | 1 , x);
if(t[now << 1].val < t[now << 1 | 1].val)
t[now] = t[now << 1];
else t[now] = t[now << 1 | 1];
}
int get_min(int l , int r , int now , int x , int y) {
if(l >= x && r <= y) {
return t[now].id;
}
int mid = (l + r) >> 1 , re1 = 1e9 , re2 = 1e9;
if(x <= mid) re1 = get_min(l , mid , now << 1 , x , y);
if(y > mid) re2 = get_min(mid + 1 , r , now << 1 | 1 , x , y);
if(re1 == 1e9) return re2;
if(re2 == 1e9) return re1;
if(s1[re1] < s1[re2]) return re1;
else return re2;
}
int main() {
scanf("%d" , &n);
scanf("%s" , s + 1);
if(n == 1) {
if(s[1] == 'p') printf("1");
else printf("0");
return 0;
}
for (int i = 1; i <= n; ++i) {
if(s[i] == 'j') s1[i] = s1[i - 1] + 1;
else s1[i] = s1[i - 1] - 1;
}
build(1 , n + 1 , 1);
s1[n + 1] = -1e9;
for (int i = 0; i < n; ++i) {
ans[i] = find(1 , n + 1 , 1 , s1[i]);
if(i) update(1 , n , 1 , i);
s1[i] = 0;
}
s1[n] = 0;
for (int i = n; i >= 1; --i) {
if(s[i] == 'j') s1[i] = s1[i + 1] + 1;
else s1[i] = s1[i + 1] - 1;
}
s1[n + 1] = -1e9;
build(1 , n + 1 , 1);
int Ans = 0;
for (int i = 1; i <= n; ++i) {
int t = get_min(1 , n + 1 , 1 , i , ans[i - 1]);
Ans = max(Ans , max(t - 1 , i) - i + 1);
}
printf("%d" , Ans);
return 0;
}
温馨提示,如果您的代码(OJ)上(40)分,洛谷上(80)分参考以下数据:
5
ppppp