Part 1
有点难😅
考虑暴力做法的本质🤔
发现瓶颈在于找到所有的作用区间,观察性质发现作用的区间和之前队列中有多少元素是无关的
用分块优化之(类似于 大步小步),可以做到 O ( n n log n ) O(n\sqrt{n}\log n) O(nnlogn)
考虑优化掉 log \log log🤔
发现对于一段相同的区间,其对应的 e d i ed_i edi(即作用区间的右端点)是单调的,因此可以考虑将每个 [ l , r ] [l,r] [l,r]拆分成若干个整块和至多 2 2 2个散块,然后对所有块取 max \max max就能得到 e d i ed_i edi
对于整块,用双指针暴力移动之,如果仍用线段树来维护那么复杂度不变;但是发现对于整块的修改可以直接打标记,而每个修改至多拆分成两个散块,因此均摊正确;
对于散块,考虑拆分成 单个队列,发现指针的单次移动为 O ( 1 ) O(1) O(1),因此复杂度正确;
总复杂度 O ( n n ) O(n\sqrt{n}) O(nn)。
Part 2
可以把操作区间在线段树上划分成 log n \log n logn个区间,然后每个位置有一个限制 a i a_i ai,表示如果每个位置的 a i a_i ai都减成 ≤ 0 \le 0 ≤0了那么作用区间就找到了。考虑建立两颗线段树,一颗维护上述限制,对于散块(即不完整的区间修改)就暴力在上面改;再建立一颗以时间为下标的线段树,整块的操作就打在这颗线段树上,查询的时候在上面二分一下即可。注意要双指针维护。
因为散块只有 n log n n\log n nlogn个,所以这个做法的复杂度是 O ( n log 2 n ) O(n\log^2 n) O(nlog2n)。
听说甚至跑不过分块。太逊了。
Part 3
PKUWC2024 DAY2 T3:
编号为 1 ∼ n 1\sim n 1∼n 的栈,有 m m m 次操作形如:
1 l r x y
:对编号为
l
∼
r
l\sim r
l∼r 的栈插入
x
x
x 个
y
y
y。
2 l r w
:对编号为
l
∼
r
l\sim r
l∼r 的栈分别执行
w
w
w 次弹栈操作。
3 k p q
:求编号为
k
k
k 的栈中第
p
∼
q
p \sim q
p∼q 个元素的和,没有的元素视为
0
0
0。
n , m ≤ 1 0 5 n,m\le 10^5 n,m≤105。
首先吐槽一句,个人不是很喜欢这种数据结构题。
首先,因为是单点询问,所以可以考虑扫描位置,维护时间轴上发生的操作(说白了就是把时间看成下标),然后对时间分块。显然可以做到 O ( n n log n ) O(n\sqrt{n\log n}) O(nnlogn)。
可以用单侧递归线段树做(楼房重建)。对于线段树上一个节点,维护区间内所有操作的效果,即push数目,pop数目,push的数之和。查询的时候从后往前处理,记录时间后方有几个pop,如果pop数大于右子树的push数就不用递归右子树,否则左子树的信息可以用作差求出,根据要查询的前缀的长度递归即可。复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2n)。
upd on 2024/2/3 :添加了线段树做法。
#include<bits/stdc++.h>
#define fi first
#define se second
#define ll long long
#define pb push_back
#define db double
#define inf 0x3f3f3f3f
using namespace std;
const int N=1e5+5;
const int B=305;
int n,m,a[N];
int bl[N];
struct query{
int l,r,x;
}q[N];
int res[N],tag,ps,L,R,len;
struct node{
int max,tag;
}t[N<<2];
void pushup(int p){
t[p].max=max(t[p<<1].max,t[p<<1|1].max);
}
void build(int p,int l,int r){
t[p].tag=0;
if(l==r){
t[p].max=a[L+l-1];
return;
}int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
pushup(p);
}
void add(int p,int x){
t[p].max+=x,t[p].tag+=x;
}
void pushdown(int p){
if(t[p].tag){
add(p<<1,t[p].tag),add(p<<1|1,t[p].tag),t[p].tag=0;
}
}
void modify(int p,int l,int r,int ql,int qr,int x){
if(ql<=l&&r<=qr){
add(p,x);
return;
}int mid=l+r>>1;pushdown(p);
if(ql<=mid)modify(p<<1,l,mid,ql,qr,x);
if(mid<qr)modify(p<<1|1,mid+1,r,ql,qr,x);
pushup(p);
}
void Add(int pos,int f){
if(pos>m)return;
int l=q[pos].l,r=q[pos].r,x=bl[l],y=bl[r];
if(x==y&&x==ps){
modify(1,1,len,l-L+1,r-L+1,f);
}
if(x!=y&&x==ps){
modify(1,1,len,l-L+1,len,f);
}
if(x!=y&&y==ps){
modify(1,1,len,1,r-L+1,f);
}
if(x<ps&&ps<y){
tag+=f;
}
}
int sum[N],rk[N];
int s[N],cnt;
vector<int>nums;
int c[N];
int lastans;
vector<int>vec1[N];
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m;for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)bl[i]=(i-1)/B+1;
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r>>q[i].x;
}
for(int i=1;i<=bl[n];i++){
ps=i;L=(i-1)*B+1,R=min(n,i*B),len=R-L+1;
build(1,1,len);
int ql=1,qr=0;nums.clear();tag=0;
for(int j=1;j<=m;j++){
int l=q[j].l,r=q[j].r;
int x=bl[l],y=bl[r];
sum[j]=sum[j-1];
if(x<i&&i<y){
while(qr<j)Add(++qr,-1);
while(ql<j)Add(ql++,1);
while(qr<=m&&t[1].max+tag>=0)Add(++qr,-1);
res[j]=max(res[j],qr-1);sum[j]++;rk[sum[j]]=j;
}
if(x==y&&x==ps)nums.pb(j);
if(x!=y&&x==ps)nums.pb(j);
if(x!=y&&y==ps)nums.pb(j);
}
//处理单点
for(int j=L;j<=R;j++){
cnt=0;
for(auto e:nums){
int l=q[e].l,r=q[e].r;
if(l<=j&&j<=r)s[++cnt]=e;
}s[cnt+1]=m+1;
int ql=0;
for(int k=1;k<=cnt;k++){
if(cnt-k+1+sum[m]-sum[s[k]]<=a[j]){
res[s[k]]=m;
continue;
}
ql=max(ql,k);
while(ql<=cnt&&sum[s[ql]]-sum[s[k]]+ql-k+1<=a[j]){
ql++;
}
if(sum[s[ql]-1]-sum[s[k]]+ql-k==a[j]){
res[s[k]]=max(res[s[k]],s[ql]-1);
}
else{
res[s[k]]=max(res[s[k]],rk[a[j]+sum[s[k]]+k-ql+1]-1);
}
}
}
}
for(int i=1;i<=m;i++){
int x=q[i].x;
vec1[res[i]+1].pb(x);
}
for(int i=1;i<=m;i++){
int x=q[i].x;
if(++c[x]==1)lastans++;
for(auto e:vec1[i])if(--c[e]==0)lastans--;
cout<<lastans<<"\n";
}
}