• CF997E Good Subsegments


    一、题目

    点此看题

    二、解法

    这题真的比较清新而且有意思,你可以先做一下弱化版

    这种区间套区间的问题猫树是通用解法,但是需要 (O(nlog^2n)) 而且口味很重,你可以把这道题的猫树解法魔改一下。

    更好的做法还是移动右端点,维护每个左端点的历史答案,也就是当这个左端点被激活后每个右端点的贡献都要记录在这个点上,询问的时候把询问区间的所有左端点贡献求和就可以得到答案。本题我们可以考虑维护这个贡献和,对于一个新的右端点我们在根上打一个标记 (tim)

    那么根的贡献和会增加全局最小值个数,然后我们考虑如何下传,如果当前节点和儿子的最小值一样,就把儿子的贡献增加它的最小值个数。现在只考虑这个统计贡献操作是没有问题的,但是如果和区间加法标记混用会不会出问题呢?你发现区间加法对统计贡献是不影响的,因为最小值的分布情况是不变的。

    那么把询问离线下来做即可,时间复杂度 (O(nlog n))

    三、总结

    这种区间套区间问题,历史思想很重要,我至今见过两种用法:

    • 如果记录历史操作和修改操作相互干扰,可以尝试使用矩阵乘法避免复杂讨论。
    • 如果两种操作之间互不干扰,并且需要先判定再记录贡献,可以直接下传标记。
    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 120005;
    const int N = M*4;
    const int inf = 0x3f3f3f3f;
    #define ll long long
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,t1,t2,a[M],s1[M],s2[M];ll ans[M],sum[4*M];
    int mi[4*M],num[4*M],fl[4*M],tim[4*M];
    struct node
    {
    	int l,r,id;
    	bool operator < (const node &b) const
    	{
    		return r<b.r;
    	}
    }q[M];
    void contri(int i,int x)
    {
    	if(!x) return ;
    	sum[i]+=1ll*x*num[i];tim[i]+=x;
    }
    void build(int i,int l,int r)
    {
    	mi[i]=inf;num[i]=r-l+1;
    	if(l==r) return ;
    	int mid=(l+r)>>1;
    	build(i<<1,l,mid);
    	build(i<<1|1,mid+1,r);
    }
    void down(int i)
    {
    	if(fl[i])
    	{
    		mi[i<<1]+=fl[i];fl[i<<1]+=fl[i];
    		mi[i<<1|1]+=fl[i];fl[i<<1|1]+=fl[i];
    		fl[i]=0;
    	}
    	if(tim[i])
    	{
    		if(mi[i]==mi[i<<1]) contri(i<<1,tim[i]);
    		if(mi[i]==mi[i<<1|1]) contri(i<<1|1,tim[i]);
    		tim[i]=0;
    	}
    }
    void add(int i,int l,int r,int L,int R,int c)
    {
    	if(L>r || l>R) return ;
    	if(L<=l && r<=R)
    	{
    		fl[i]+=c;mi[i]+=c;
    		return ;
    	}
    	int mid=(l+r)>>1;down(i);
    	add(i<<1,l,mid,L,R,c);
    	add(i<<1|1,mid+1,r,L,R,c);
    	mi[i]=min(mi[i<<1],mi[i<<1|1]);num[i]=0;
    	if(mi[i]==mi[i<<1]) num[i]+=num[i<<1];
    	if(mi[i]==mi[i<<1|1]) num[i]+=num[i<<1|1]; 
    }
    ll ask(int i,int l,int r,int L,int R)
    {
    	if(L>r || l>R) return 0;
    	if(L<=l && r<=R) return sum[i];
    	int mid=(l+r)>>1;down(i);
    	return ask(i<<1,l,mid,L,R)
    	+ask(i<<1|1,mid+1,r,L,R);
    }
    signed main()
    {
    	n=read();build(1,1,n);
    	for(int i=1;i<=n;i++)
    		a[i]=read();
    	m=read();
    	for(int i=1;i<=m;i++)
    		q[i].l=read(),q[i].r=read(),q[i].id=i;
    	sort(q+1,q+1+m);
    	for(int i=1,j=1;i<=n;i++)
    	{
    		add(1,1,n,i,i,-inf+i);
    		while(t1 && a[s1[t1]]>=a[i])
    		{
    			add(1,1,n,s1[t1-1]+1,s1[t1],a[s1[t1]]);
    			t1--;
    		}
    		while(t2 && a[s2[t2]]<=a[i])
    		{
    			add(1,1,n,s2[t2-1]+1,s2[t2],-a[s2[t2]]);
    			t2--;
    		}
    		add(1,1,n,s1[t1]+1,i,-a[i]);
    		add(1,1,n,s2[t2]+1,i,a[i]);
    		s1[++t1]=i;s2[++t2]=i;
    		contri(1,1);
    		while(j<=m && q[j].r==i)
    		{
    			ans[q[j].id]=ask(1,1,n,q[j].l,i);
    			j++;
    		}
    	}
    	for(int i=1;i<=m;i++)
    		printf("%lld
    ",ans[i]);
    }
    
  • 相关阅读:
    java堆和栈的区别
    什么是java 序列化,如何实现java 序列化?
    HashMap和Hashtable的区别
    [LeetCode] 548. Split Array with Equal Sum 分割数组成和相同的子数组
    [LeetCode] 533. Lonely Pixel II 孤独的像素 II
    [LeetCode] 531. Lonely Pixel I 孤独的像素 I
    [LeetCode] 205. Isomorphic Strings 同构字符串
    [LeetCode] 351. Android Unlock Patterns 安卓解锁模式
    Python list、tuple、dict区别
    Minimum number of swaps required to sort an array
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15557725.html
Copyright © 2020-2023  润新知