• [CF997E] Good Subsegments


    前言

    我后悔了,要是拉这个加强版就不至于人均切 T4 了。

    题目

    洛谷

    CF

    讲解

    CF526F Pudding Monsters 的加强版,没做过的建议先去做一下,至少要熟悉那道题的思路。

    我们还是移动右端点,用单调栈+线段树维护一下 \(max-min+cnt\) 的值,思考一个好区间对之后的区间的贡献是什么?

    其实就是1啦,只要后面的区间的左端点达到现在这个好区间的左端点,那么就会有贡献,所以我们考虑维护一个标记,将其放在好区间的左端点,一旦后面的区间包含这个左端点,那么就可以获得它的贡献。

    那么我们应该怎么快速给这些好区间的左端点加上一个1的标记呢?它们的通性不就是全局最小值吗?所以我们只需要把所以是最小值的地方都加上这个标记即可。由于之前的单调栈+线段树的区间加操作不会影响一个顶点左右儿子最小值相对关系,所以只要儿子和当前节点的最小值相同,我们就可以放心大胆地下传这个标记。

    时间复杂度 \(O(n\log_2n)\)

    代码

    easy to understand
    //12252024832524
    #include <bits/stdc++.h>
    #define TT template<typename T>
    using namespace std;
    
    typedef long long LL;
    const int MAXN = 120005;
    int n,m;
    int a[MAXN];
    LL ans[MAXN];
    vector<pair<int,int> > q[MAXN];
    
    LL Read()
    {
    	LL x = 0,f = 1; char c = getchar();
    	while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
    	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
    	return x * f;
    }
    TT void Put1(T x)
    {
    	if(x > 9) Put1(x/10);
    	putchar(x%10^48);
    }
    TT void Put(T x,char c = -1)
    {
    	if(x < 0) putchar('-'),x = -x;
    	Put1(x); if(c >= 0) putchar(c);
    }
    TT T Max(T x,T y){return x > y ? x : y;}
    TT T Min(T x,T y){return x < y ? x : y;}
    TT T Abs(T x){return x < 0 ? -x : x;}
    
    #define lc (x<<1)
    #define rc (x<<1|1)
    int MIN[MAXN<<2],lz[MAXN<<2],cnt[MAXN<<2],tim[MAXN<<2];
    LL val[MAXN<<2];
    void calc(int x,LL v)
    {
    	val[x] += v * cnt[x];
    	tim[x] += v;
    }
    void down(int x)
    {
    	if(lz[x])
    	{
    		MIN[lc] += lz[x]; lz[lc] += lz[x];
    		MIN[rc] += lz[x]; lz[rc] += lz[x];
    		lz[x] = 0;
    	}
    	if(tim[x])//这两个if写反了,调了5分钟
    	{
    		if(MIN[x] == MIN[lc]) calc(lc,tim[x]);
    		if(MIN[x] == MIN[rc]) calc(rc,tim[x]);
    		tim[x] = 0;
    	}
    }
    void up(int x)
    {
    	MIN[x] = Min(MIN[lc],MIN[rc]); cnt[x] = 0;
    	if(MIN[x] == MIN[lc]) cnt[x] += cnt[lc];
    	if(MIN[x] == MIN[rc]) cnt[x] += cnt[rc];
    }
    void Build(int x,int l,int r)
    {
    	cnt[x] = r-l+1;
    	if(l == r) return;
    	int mid = (l+r) >> 1;
    	Build(lc,l,mid); Build(rc,mid+1,r);
    }
    void Add(int x,int l,int r,int ql,int qr,int v)
    {
    	if(ql <= l && r <= qr)
    	{
    		MIN[x] += v;
    		lz[x] += v;
    		return;
    	}
    	int mid = (l+r) >> 1;
    	down(x);
    	if(ql <= mid) Add(lc,l,mid,ql,qr,v);
    	if(mid+1 <= qr) Add(rc,mid+1,r,ql,qr,v);
    	up(x);
    }
    LL Query(int x,int l,int r,int ql,int qr)
    {
    	if(ql <= l && r <= qr) return val[x];
    	int mid = (l+r) >> 1;LL ret = 0;
    	down(x);
    	if(ql <= mid) ret += Query(lc,l,mid,ql,qr);
    	if(mid+1 <= qr) ret += Query(rc,mid+1,r,ql,qr);
    	return ret;
    }
    int s1[MAXN],tl1,s2[MAXN],tl2;
    
    int main()
    {
    //	freopen(".in","r",stdin);
    //	freopen(".out","w",stdout);
    	n = Read();
    	for(int i = 1;i <= n;++ i) a[i] = Read();
    	m = Read();
    	for(int i = 1;i <= m;++ i)
    	{
    		int l = Read();
    		q[Read()].emplace_back(make_pair(l,i));
    	}
    	Build(1,1,n);
    	for(int i = 1;i <= n;++ i)
    	{
    		Add(1,1,n,1,i,-1);
    		while(tl1 && a[i] > a[s1[tl1]]) Add(1,1,n,s1[tl1-1]+1,s1[tl1],-a[s1[tl1]]),--tl1;
    		while(tl2 && a[i] < a[s2[tl2]]) Add(1,1,n,s2[tl2-1]+1,s2[tl2],a[s2[tl2]]),--tl2;
    		s1[++tl1] = s2[++tl2] = i;
    		Add(1,1,n,s1[tl1-1]+1,s1[tl1],a[s1[tl1]]);
    		Add(1,1,n,s2[tl2-1]+1,s2[tl2],-a[s2[tl2]]);
    		calc(1,1);
    		for(auto A : q[i]) ans[A.second] = Query(1,1,n,A.first,i);
    	}
    	for(int i = 1;i <= m;++ i) Put(ans[i],'\n');
    	return 0;
    }
    
  • 相关阅读:
    重温数据结构与算法(1) 构建自己的时间测试类
    读<<CLR via C#>>总结(11) 详谈事件
    读<<CLR via C#>>总结(13) 详谈泛型
    重温数据结构与算法(2) 编程中最常用,最通用的数据结构数组和ArrayList
    由String类的Split方法所遇到的两个问题
    读<<CLR via C#>>总结(6) 详谈实例构造器和类型构造器
    让我们都建立自己的知识树吧
    读<<CLR via C#>>总结(5) 如何合理使用类型的可见性和成员的可访问性来定义类
    读<<CLR via C#>>总结(10) 详谈委托
    读<<CLR via C#>>总结(4) 值类型的装箱和拆箱
  • 原文地址:https://www.cnblogs.com/PPLPPL/p/15557969.html
Copyright © 2020-2023  润新知