C. [Ynoi2005] rmscne
直接做不好维护,考虑扫描线。用线段树对每个位置 \(i\) 维护 \(p_i\) 表示 \([i,p_i]\) 是 \([i,r]\) 的最小的合法子区间。维护方式很简单,当加入 \(a_r\) 时,设上一次出现的位置为 \(j\),则 \([j+1,r]\) 的 \(p\) 值应赋为 \(r\)。
考虑询问 \([l,r]\),我们要找到最大的 \(c\) 使得 \([l,c]\) 是 \([l,r]\) 的合法子区间,答案即为 \(\min_{i=l}^{c}\{p_i-i\}\)。考虑当加入 \(r\) 时,上一次出现的位置 \(j\) 就没用了,\(j\) 对应的 \(c\) 就应指向 \(j+1\),使用并查集即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
#define pb push_back
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=2e6+5,inf=1e9;
int n,m,a[maxn],fa[maxn],tr[maxn<<2],tag[maxn<<2],pos[maxn],ans[maxn];
vector<pii> q[maxn];
il int find(int x){return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void pushup(int id){tr[id]=min(tr[lid],tr[rid]);
}
il void pushtag(int id,int l,int r,int x){tag[id]=x,tr[id]=x-r+1;
}
il void pushdown(int id,int l,int r){if(tag[id]){int mid=(l+r)>>1;pushtag(lid,l,mid,tag[id]);pushtag(rid,mid+1,r,tag[id]);tag[id]=0;}
}
il void upd(int id,int L,int R,int l,int r,int x){if(L>=l&&R<=r){pushtag(id,L,R,x);return ;}pushdown(id,L,R);int mid=(L+R)>>1;if(l<=mid){upd(lid,L,mid,l,r,x);}if(r>mid){upd(rid,mid+1,R,l,r,x);}pushup(id);
}
il int query(int id,int L,int R,int l,int r){if(L>=l&&R<=r){return tr[id];}pushdown(id,L,R);int mid=(L+R)>>1,res=inf;if(l<=mid){res=min(res,query(lid,L,mid,l,r));}if(r>mid){res=min(res,query(rid,mid+1,R,l,r));}return res;
}
int main(){ios::sync_with_stdio(0),cin.tie(0);cin>>n;for(int i=1;i<=n;i++){cin>>a[i];fa[i]=i;}cin>>m;for(int i=1,l,r;i<=m;i++){cin>>l>>r;q[r].pb(mp(l,i));}for(int i=1;i<=n;i++){upd(1,1,n,pos[a[i]]+1,i,i);fa[find(pos[a[i]])]=find(pos[a[i]]+1);pos[a[i]]=i;for(pii t:q[i]){int j=t.fir,id=t.sec;ans[id]=query(1,1,n,j,find(j));}}for(int i=1;i<=m;i++){cout<<ans[i]<<'\n';}return 0;
}
}
int main(){return asbt::main();}
D. [Ynoi2019 模拟赛] Yuno loves sqrt technology II
假设 \(n,m\) 同阶。(实则是嫌麻烦)
这个问题显然可以莫队 + 树状数组做,莫队过程中当右端点右移时给答案加上这个区间中比 \(a_r\) 大的个数,其他类似。然而时间复杂度 \(O(n\sqrt{n}\log n)\),需要进一步优化。
莫队二次离线
顾名思义,虽然莫队已经是离线算法了,但其中的一些区间查询(如查询当前 \([l,r]\) 中有多少个数比 \(a_r\) 大)仍然需要较高复杂度,于是我们将这些查询再离线下来,用扫描线的方式解决。
回到本题,这个查询显然可以差分成 \([1,r]\) 和 \([1,l-1]\)。\([1,r]\) 可以直接使用树状数组预处理出来,不成问题;我们就将 \([1,l-1]\) 这一部分离线下来。然后对 \(l\) 进行扫描线,我们将在值域上进行 \(O(n\sqrt{n})\) 次区间查询和 \(O(n)\) 次单点修改,于是再分块即可。注意这样求出来的答案实则是相邻两个询问答案之差,需要再进行前缀和。
然而空间复杂度爆炸。考虑莫队左右端点移动的过程,每个 \(l\) 会对应一段区间的 \(r\),于是将它存成区间即可做到空间线性。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5,B=316,maxb=319;
int n,m,a[maxn],lsh[maxn];
int bnm,st[maxb],ed[maxb],bel[maxn];
int pre[2][maxn],ans[maxn];
struct node{int l,r,id,typ;node(int l=0,int r=0,int id=0,int typ=0):l(l),r(r),id(id),typ(typ){}il bool operator<(const node &x)const{return bel[l]!=bel[x.l]?l<x.l:bel[l]&1?r<x.r:r>x.r;}
}b[maxn];
vector<node> c[maxn];
struct{#define lowbit(x) (x&-x)int tr[maxn];il void add(int p){for(;p<=n;p+=lowbit(p)){tr[p]++;}}il int query(int p){int res=0;for(;p;p-=lowbit(p)){res+=tr[p];}return res;}#undef lowbit
}F;
struct{int tr[maxn],tag[maxb];il void add(int l,int r){if(l>r){return ;}int pl=bel[l],pr=bel[r];if(pl==pr){for(int i=l;i<=r;i++){tr[i]++;}return ;}for(int i=pl+1;i<pr;i++){tag[i]++;}for(int i=l;i<=ed[pl];i++){tr[i]++;}for(int i=st[pr];i<=r;i++){tr[i]++;}}il int query(int p){return tr[p]+tag[bel[p]];}
}BL[2];
il void init(){cin>>n>>m;int cnt=0;for(int i=1;i<=n;i++){
// cerr<<"666\n";cin>>a[i];lsh[++cnt]=a[i];}sort(lsh+1,lsh+cnt+1);cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;for(int i=1;i<=n;i++){a[i]=lwrb(lsh+1,lsh+cnt+1,a[i])-lsh;F.add(a[i]);pre[0][i]=pre[0][i-1]+F.query(a[i]-1);pre[1][i]=pre[1][i-1]+F.query(n)-F.query(a[i]);}for(int i=1;i<=m;i++){cin>>b[i].l>>b[i].r;b[i].id=i;}bnm=(n+B-1)/B;for(int i=1;i<=bnm;i++){st[i]=ed[i-1]+1;ed[i]=min(ed[i-1]+B,n);for(int j=st[i];j<=ed[i];j++){bel[j]=i;}}
}
il void work(){sort(b+1,b+m+1);int l=1,r=0;for(int i=1;i<=m;i++){if(r<b[i].r){c[l-1].pb(node(r+1,b[i].r,-b[i].id,1));ans[b[i].id]+=pre[1][b[i].r]-pre[1][r];r=b[i].r;}if(l>b[i].l){c[r].pb(node(b[i].l,l-1,b[i].id,0));ans[b[i].id]-=pre[0][l-1]-pre[0][b[i].l-1];l=b[i].l;}if(r>b[i].r){c[l-1].pb(node(b[i].r+1,r,b[i].id,1));ans[b[i].id]-=pre[1][r]-pre[1][b[i].r];r=b[i].r;}if(l<b[i].l){c[r].pb(node(l,b[i].l-1,-b[i].id,0));ans[b[i].id]+=pre[0][b[i].l-1]-pre[0][l-1];l=b[i].l;}}
// cerr<<"777\n";
}
il void solve(){for(int i=1;i<=n;i++){BL[0].add(a[i]+1,n),BL[1].add(1,a[i]-1);for(node t:c[i]){for(int j=t.l;j<=t.r;j++){if(t.id>0){ans[t.id]+=BL[t.typ].query(a[j]);}else{ans[-t.id]-=BL[t.typ].query(a[j]);}}}}for(int i=1;i<=m;i++){ans[b[i].id]+=ans[b[i-1].id];}for(int i=1;i<=m;i++){cout<<ans[i]<<'\n';}
}
int main(){
// system("fc P5047_1.out my.out");
// freopen("P5047_1.in","r",stdin);
// freopen("my.out","w",stdout);ios::sync_with_stdio(0),cin.tie(0);init(),work(),solve();return 0;
}
}
signed main(){return asbt::main();}
/*
5 3
9 3 9 4 7
1 5
2 4
3 5
*/