「POI2014」沙拉餐厅 Salad Bar

这道题让我在2.29号的上午自闭了数个小时。。。

首先分享一个非常不好的习惯--做题看标签:

QQ图片20200229183529.png

于是可怜的我就被坑了。。。

看到二分答案,我首先打了个二分,想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
posted @ 2020-08-02 21:16  Reanap  阅读(177)  评论(0编辑  收藏  举报