zoukankan      html  css  js  c++  java
  • 「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
    
  • 相关阅读:
    做了一些心理学的测试,分析下个人
    做了一些心理学的测试,分析下个人
    逆转一个整数
    打印九九乘法表
    计算两个日期相差多少天
    struct的使用
    Linux Vim替换字符串的一些方法小结
    CentOS里vim基本操作
    如何创建一个后台进程
    高中是个把人分类的机器(转)
  • 原文地址:https://www.cnblogs.com/Reanap/p/13423206.html
Copyright © 2011-2022 走看看