题目描述:https://loj.ac/problem/2461
题解
非常巧妙的一道题
我们对于一个操作i:[l,r,v],求出这个操作中最后一个被pop掉的v的时间(记为ed[i])
即经历了(i,ed[i]]这些操作后,操作i的所有push进来的元素都被pop掉了
如果我们可以求出所有操作的ed,我们就可以简单求出每一时刻的答案(自行思考,存一下每个元素的出现次数即可)
如何求ed[i]?
首先,我们知道,一个元素push进了某一个队列k,如果这个队列x再被后来的操作覆盖a[k]次,这个元素就会被pop出去了
(这个和queue是否为空无关,因为它只会在溢出的时候push)
那么有以下式子
last(i,k)表示位置k在第i个操作push进来的元素会在第last_k个操作时pop掉
换一种枚举方式,我们可以枚举每一个位置对于所有操作的ed的贡献
于是我们就有了一个O(nm)的暴力:
对每一个位置进行一下two_pointers,我们可以求出当前位置k对于所有的i的last(i,k)值
具体怎么求?
首先我们知道位置k要被覆盖a[k]次
设l指针为i,r指针为last(i,k)
当l++时,如果操作l-1覆盖了位置k,那么位置k的剩余覆盖次数++
当r++时,如果操作r覆盖了位置k,那么位置k的剩余覆盖次数--
移动一下左端点,就需要一直移动右端点,直到k的剩余覆盖次数=0,此时的j就是last(i,k),再移动一下左端点求下一个i的答案
然后把每求出一个last(i,k)更新到ed[i]=max(ed,last(i,k))中
即可求出所有操作的ed值
考虑一下怎么来优化一下这个暴力
理性分析一下,我们之所以能够用two_pointers来求出每一个操作的last(i,k)值,是因为位置k的a[k]是不变的
如果我们进行一下分块。。。
则每一个块中的a的组成都是不变的,这样的话求出来的last与i也是正相关的,即也可以运用two_pointers
那么有以下式子
ed[i]=max([li,ri]覆盖的整块的最大last值,[li,ri]覆盖的零散块的最大last值)
也可以换一种枚举方式,求每一个整块和零散块对所有操作的贡献
先来考虑最简单的情况
1、整块
显然,一个整块想要被pop完,只与当前整块中的最大的剩余覆盖次数有关
所以先把mx初始化为当前块中的最大a值
注意two_pointers在计算last(i)是要先删掉操作i的贡献
当two_pointers在新加入/删除一个操作的时候又要分两种情况:
(1)、完全覆盖当前块
存一个cov标记,表示当前的整块被覆盖了多少次
在移动r指针时判断一下mx-cov是否为0即可
(2)、与当前块有交集
直接暴力修改当前块的a值,在重新计算mx即可(注意,这里不能修改真正的a值,所有要先把a值转存一下)
2、零散块
如果我们直接对每个点two_pointers的话就会把时间复杂度退化为O(nm)(甚至更高)
所以我们要利用之前整块进行two_pointers的信息来进行优化
我们发现之前的整块加操作会对当前块中的零散块造成相同的影响
所以我们存一下整块加的前缀和sum[i]表示从操作1~操作i中当前块一共经历了多少次完整覆盖
设c[i]表示第i个完全覆盖当前块的操作编号
d[i]表示第i个与当前块相交的操作编号
e[i]表示lower_bound(c+1,c+cn+1,d[i])-c-1
于是我们就可以对每个点进行two_pointers,只不过更新的ed只有编号在d集合中的ed值
(之前的整块相当于只对c集合进行更新,现在的零散块相当于只对d集合进行更新)
细节较多,需要细细考虑
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 100005
#define D 360
int n,m,a[N],b[N];
int c[N],d[N],e[N],sum[N],cn,dn;
int vis[N],num,ed[N];
int l[N],r[N],v[N];
struct node{int x,y;};
vector<node>g[N];
int main()
{
int i,j,k,mx,cov,L,R;
n=gi();m=gi();
for(i=1;i<=n;i++)a[i]=gi();
for(i=1;i<=m;i++)l[i]=gi(),r[i]=gi(),v[i]=gi();
for(L=1;L<=n;L+=D){
cn=dn=mx=cov=0;
R=min(n,L+D-1);
for(i=L;i<=R;i++)mx=max(mx,b[i]=a[i]);
for(i=1,j=0;i<=m;i++){
if(l[i]<=L&&r[i]>=R)cov--;
else if(l[i]<=R&&r[i]>=L){
for(k=max(l[i],L);k<=min(r[i],R);k++)b[k]++;
mx=0;for(k=L;k<=R;k++)mx=max(mx,b[k]);
}
while(j<=m&&mx-cov>0){
j++;
if(l[j]<=L&&r[j]>=R)cov++;
else if(l[j]<=R&&r[j]>=L){
for(k=max(l[j],L);k<=min(r[j],R);k++)b[k]--;
mx=0;for(k=L;k<=R;k++)mx=max(mx,b[k]);
}
}
sum[i]=sum[i-1];
if(l[i]<=L&&r[i]>=R){
ed[i]=max(ed[i],j);
c[++cn]=i;sum[i]++;
}
else if(l[i]<=R&&r[i]>=L)d[++dn]=i,e[dn]=cn;
}
for(i=L;i<=R;i++){
mx=a[i];
for(j=1,k=0;j<=dn;j++){
mx+=sum[d[j]]-sum[d[j-1]];
if(l[d[j]]<=i&&r[d[j]]>=i){
mx++;
while(k<dn&&mx>0){
k++;
mx-=sum[d[k]]-sum[d[k-1]];
if(l[d[k]]<=i&&r[d[k]]>=i)
mx--;
}
if(mx>0){
if(sum[m]-sum[d[k]]<mx)ed[d[j]]=max(ed[d[j]],m+1);
else ed[d[j]]=max(ed[d[j]],c[e[k]+mx]);
continue;
}
if(l[d[k]]<=i&&r[d[k]]>=i){
if(mx<0)ed[d[j]]=max(ed[d[j]],c[e[k]+mx+1]);
else ed[d[j]]=max(ed[d[j]],d[k]);
}
else ed[d[j]]=max(ed[d[j]],c[e[k]+mx]);
}
}
}
}
for(i=1;i<=m;i++){
g[i].push_back((node){v[i],1});
g[ed[i]].push_back((node){v[i],-1});
}
for(i=1;i<=m;i++){
for(j=0;j<(int)g[i].size();j++){
int x=g[i][j].x,y=g[i][j].y;
if((vis[x]+y==0)^(vis[x]==0))num+=y;
vis[x]+=y;
}
printf("%d\n",num);
}
}