「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
原文地址:https://www.cnblogs.com/Reanap/p/13423206.html