CF997E Good Subsegments

一、题目

点此看题

二、解法

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

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

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

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

那么把询问离线下来做即可,时间复杂度 \(O(n\log 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\n",ans[i]);
}
上一篇:【无标题】


下一篇:FileInputStream和FileOutputStream详解