「POI2014」沙拉餐厅 Salad Bar
这道题让我在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