【2022NOI模拟赛01】漏网之鱼 做题记录

给定一个长度为 nn 的序列 aa,有 qq 次询问,每次询问给定 L,RL,R,求

l=LRr=lRmex({al,al+1,al+2,,ar})\sum\limits_{l=L}^{R}\sum\limits_{r=l}^{R} \operatorname{mex}(\{a_l,a_{l+1},a_{l+2},\dots,a_r\})

其中 mex(S)\operatorname{mex}(S) 表示 SS 中最小的未出现的非负整数。

1n,q1061\le n,q\le 10^6

考虑只有一次询问的时候怎么做,首先 l=1l=1 时每个 [l,r][l,r]mex\operatorname{mex} 是可以 O(n)O(n) 预处理的,那么接下来考虑把 ll 往右移,即删掉 ala_l 后每个 [l,r][l,r]mex\operatorname{mex}

不如设当前 [l,i][l,i]mex\operatorname{mex}meime_i,那么删除 ala_l 相当于让 me[l+1,nxtl1]me_{[l+1,nxt_{l}-1]}ala_lmin\min,其中 nxtlnxt_{l}ala_l 下一次出现的位置。

不难发现可以用线段树维护 meme,由于 meme 有单调性,所以区间取 min\min 可以通过线段树二分找到 >al>a_l 的后缀来转换成区间赋值。那么只有一个询问就可以在 O(nlogn)O(n\log n) 的时间内解决。

考虑有多个询问的情况,显然一次询问 [L,R][L,R] 可以拆成 l[1,R],r[L,R]l\in[1,R],r\in[L,R] 的答案减掉 l[1,L1],r[L,R]l\in[1,L-1],r\in[L,R] 的答案,那么问题就变成要求 l[1,x],r[L,R]l\in[1,x],r\in[L,R] 的答案。

考虑把询问离线下来,按照 xx 升序排序,然后把 ll11 往右移动。这样问题就变成了要求 [L,R][L,R]meme 的历史和(每次修改后的值累加的结果,可以看作是在时间维上做前缀和)。

维护历史和有很多种写法,比较通用的是给每个位置维护一个一次函数 k×tme+bk\times tme+b,其中 tmetme 表示当前时间,kk 存的是上一次修改之后的 meime_i。在时间 ii 修改一个位置的 memexx 的时候,设 bb' 为新的 bbkk' 为新的 kk,那么有:

b=i×(kx)+bk=xb'=i\times (k-x)+b\\ k'=x

注意到这个东西只有在区间内每个位置的 kk 都相等时才可以用区间加快速维护,但是不难发现一个区间改完之后区间内的每个位置的 kk 就会变成一样的,所以每次修改暴力找到所有区间内 kk 相等的区间是对的,这样做每一次修改的时间复杂度均摊为 O(logn)O(\log n),那么总时间复杂度就是 O((n+q)logn)O((n+q)\log n)

代码如下:

#include <iostream>
#include <cstdio>
#include <vector>

using namespace std;

const int S=1000005;

struct ask
{
	int l,r,bse,id;
};

struct node
{
	long long ks,bs;
	long long mnk,mxk,tk,tb;
};

int IDX;
int n,q,a[S];
node tr[S<<2];
vector<ask> vec[S];
bool vis[S];
int b[S],lst[S],nxt[S];
long long ans[S];

inline void upd(int u)
{
	tr[u].ks=tr[u<<1].ks+tr[u<<1|1].ks;
	tr[u].bs=tr[u<<1].bs+tr[u<<1|1].bs;
	tr[u].mnk=min(tr[u<<1].mnk,tr[u<<1|1].mnk);
	tr[u].mxk=max(tr[u<<1].mxk,tr[u<<1|1].mxk);
}

void build(int u,int l,int r)
{
	if(l==r) return tr[u].ks=tr[u].mnk=tr[u].mxk=b[l],void();
	int mid=l+r>>1;
	build(u<<1,l,mid),build(u<<1|1,mid+1,r);
	upd(u);
}

inline void addtag(int u,int l,int r,long long k,long long b)
{
	int siz=r-l+1;
	tr[u].ks+=k*siz,tr[u].bs+=b*siz;
	tr[u].mnk+=k,tr[u].mxk+=k;
	tr[u].tk+=k,tr[u].tb+=b;
}

inline void dwntag(int u,int l,int r)
{
	if(tr[u].tk==0&&tr[u].tb==0) return;
	int mid=l+r>>1;
	addtag(u<<1,l,mid,tr[u].tk,tr[u].tb);
	addtag(u<<1|1,mid+1,r,tr[u].tk,tr[u].tb);
	tr[u].tk=tr[u].tb=0;
}

int quepos(int u,int l,int r,int L,int R,long long k)
{
	if(l>R||r<L||tr[u].mxk<=k) return -1;
	if(l==r) return l;
	dwntag(u,l,r);
	int mid=l+r>>1;
	if(L<=mid&&tr[u<<1].mxk>k) return quepos(u<<1,l,mid,L,R,k);
	return quepos(u<<1|1,mid+1,r,L,R,k);
}

void upda(int u,int l,int r,int L,int R,int tme,long long k)
{
	if(l>R||r<L) return;
	if(l>=L&&r<=R&&tr[u].mxk==tr[u].mnk)
	{
		int len=r-l+1;
		addtag(u,l,r,k-tr[u].mxk,tme*(tr[u].mxk-k));
		return;
	}
	dwntag(u,l,r);
	int mid=l+r>>1;
	if(L<=mid) upda(u<<1,l,mid,L,R,tme,k);
	if(R>=mid+1) upda(u<<1|1,mid+1,r,L,R,tme,k);
	upd(u);
}

long long quer(int u,int l,int r,int L,int R,int x)
{
	if(l>R||r<L) return 0;
	if(l>=L&&r<=R) return 1ll*tr[u].ks*x+tr[u].bs;
	dwntag(u,l,r);
	int mid=l+r>>1;
	long long res=0;
	if(L<=mid) res+=quer(u<<1,l,mid,L,R,x);
	if(R>=mid+1) res+=quer(u<<1|1,mid+1,r,L,R,x);
	return res;
}

int main()
{
	scanf("%d",&IDX);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]=min(a[i],n+1);
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		int l,r;
		scanf("%d%d",&l,&r);
		vec[r].push_back((ask){l,r,1,i});
		if(l>1) vec[l-1].push_back((ask){l,r,-1,i});
	}
	for(int i=1,p=0;i<=n;i++)
	{
		vis[a[i]]=true;
		while(vis[p]) p++;
		b[i]=p;
	}
	build(1,1,n);
	for(int i=0;i<=n+1;i++) lst[i]=n+1;
	for(int i=n;i>=1;i--)
	{
		nxt[i]=lst[a[i]];
		lst[a[i]]=i;
	}
	for(int i=1;i<=n;i++)
	{
		for(ask u:vec[i])
		{
			ans[u.id]+=u.bse*quer(1,1,n,u.l,u.r,i);
		}
		int lb=i+1,rb=nxt[i]-1;
		if(lb<=rb)
		{
			int pos=quepos(1,1,n,lb,rb,a[i]);
			if(pos!=-1) upda(1,1,n,pos,rb,i,a[i]);
		}
		upda(1,1,n,i,i,i,0);
	}
	for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);
	return 0;
}