III.CF1093E Intersection of Permutations
首先,我们如果令\(c[i]\)表示\(b[i]\)在数组\(a\)中出现的位置,
那么对于一次询问,答案就是\(c\)中下标在\([l_2,r_2]\)间的数字中,值位于\([l_1,r_1]\)间的数量。
思路1.树状数组套权值线段树。
很明显,这题的操作如果不带修可以用主席树完成(下标在\([l_2,r_2]\)里面的树上进行值域查询)。带了修改,则套上树状数组完事。
代码:
#include<bits/stdc++.h>
using namespace std;
#define mid ((l+r)>>1)
int n,m,root[200100],cnt,bin[10010000],tp,num[200100],a[200100],b[200100];
struct node{
int lson,rson,sum;
}seg[20001000];
int newid(){
if(!tp)return ++cnt;
return bin[tp--];
}
void ADD(int &x,int l,int r,int P,int val){
if(l>P||r<P)return;
if(!x)x=newid();
seg[x].sum+=val;
if(l!=r)ADD(seg[x].lson,l,mid,P,val),ADD(seg[x].rson,mid+1,r,P,val);
if(!seg[x].sum)bin[++tp]=x,x=0;
}
void add(int x,int y,int z){
while(x<=n)ADD(root[x],1,n,y,z),x+=x&-x;
}
int Query(int x,int l,int r,int L,int R){
if(!x||l>R||r<L)return 0;
if(L<=l&&r<=R)return seg[x].sum;
return Query(seg[x].lson,l,mid,L,R)+Query(seg[x].rson,mid+1,r,L,R);
}
int ask(int l,int r,int L,int R){
l--;
int res=0;
while(r)res+=Query(root[r],1,n,L,R),r-=r&-r;
while(l)res-=Query(root[l],1,n,L,R),l-=l&-l;
return res;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++)scanf("%d",&x),a[x]=i;
for(int i=1,x;i<=n;i++)scanf("%d",&x),b[i]=a[x];
for(int i=1;i<=n;i++)add(i,b[i],1);
for(int i=1,t1,t2,t3,t4,t5;i<=m;i++){
scanf("%d",&t1);
if(t1==1)scanf("%d%d%d%d",&t2,&t3,&t4,&t5),printf("%d\n",ask(t4,t5,t2,t3));
else scanf("%d%d",&t2,&t3),add(t2,b[t2],-1),add(t3,b[t3],-1),swap(b[t2],b[t3]),add(t2,b[t2],1),add(t3,b[t3],1);
}
return 0;
}
思路2.奇奇怪怪的分块-ver1.0
我们在数组\(c\)上分块,在每个块内排序。每次整块二分,散块直接暴力,复杂度\(O(n\sqrt{n}\log n)\)。
因为实现很糟糕,因此它T掉了。
TLE代码:
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int BBB=600;
int n,m,BLK[200100],ra[200100],c[200100],d[200100],lp[1000],lim;
inline void SWAP(int x,int y){
swap(c[x],c[y]);
for(register int i=lp[BLK[x]];i<lp[BLK[x]+1];i++)d[i]=c[i];
sort(d+lp[BLK[x]],d+lp[BLK[x]+1]);
for(register int i=lp[BLK[y]];i<lp[BLK[y]+1];i++)d[i]=c[i];
sort(d+lp[BLK[y]],d+lp[BLK[y]+1]);
}
inline int CALC(int l,int r,int L,int R){
register int res=0;
if(BLK[L]==BLK[R]){
for(register int i=L;i<R;i++)res+=(c[i]>=l&&c[i]<r);
return res;
}
for(register int i=L;i<lp[BLK[L]+1];i++)res+=(c[i]>=l&&c[i]<r);
for(register int i=lp[BLK[R]];i<R;i++)res+=(c[i]>=l&&c[i]<r);
for(register int i=BLK[L]+1;i<BLK[R];i++)res+=lower_bound(d+lp[i],d+lp[i+1],r)-lower_bound(d+lp[i],d+lp[i+1],l);
return res;
}
inline void read(int &x){
x=0;
register char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
inline int read(){
register int x=0;
register char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
inline void print(int x){
if(x<=9)putchar('0'+x);
else print(x/10),putchar('0'+x%10);
}
int main(){
read(n),read(m);
for(register int i=0;i<n;i++)ra[read()]=i,BLK[i]=i/BBB;
for(register int i=0;i<n;i++)c[i]=d[i]=ra[read()];
lim=BLK[n-1]+1;
for(register int i=0;i<lim;i++)lp[i]=i*BBB;
lp[lim]=n,BLK[n]=lim;
for(register int i=0;i<lim;i++)sort(d+lp[i],d+lp[i+1]);
for(register int i=1,t1,t2,t3,t4,t5;i<=m;i++){
read(t1);
if(t1==1)read(t2),read(t3),read(t4),read(t5),t2--,t4--,print(CALC(t2,t3,t4,t5)),putchar('\n');
else read(t2),read(t3),t2--,t3--,SWAP(t2,t3);
}
return 0;
}
思路3.奇奇怪怪的分块-ver2.0
设块长为\(k\),我们分析一下各个操作的复杂度:
整块复杂度为\(O(k\log\dfrac{n}{k})=O(k\log n)\)
散块复杂度为\(O(\dfrac{n}{k})\)
修改复杂度为\(O(\dfrac{n}{k}\log\dfrac{n}{k})=\dfrac{n}{k}\log n\)
则当\(k=\sqrt{n}\)时复杂度最优,为\(O(\sqrt{n}\log n)\)。
我们如果只考虑询问复杂度的话,这个\(k\)是可以取到\(\sqrt{n\log n}\)的。关键是修改。
有两种思路。
-
考虑将修改也分块。将修改储存下来,每累计\(\sqrt{n\log n}\)个修改后,就暴力\(O(n\log n)\)全部推平重新排序。则每次询问时,只需要\(O(\sqrt{n\log n})\)遍历所有修改并更新答案即可。复杂度\(O(\sqrt{n\log n})\)。
-
原本的修改,我们是\(\dfrac{n}{k}\log n\)重新排序的;发现修改只作用于一个位置;故可以直接\(O(k)\)插入排序,或者直接套上
std::set
让修改优化到\(O(\log k)\)。(实际上,套上set
后就已经相当于一种树套树了,是分块套平衡树)
代码:
#pragma GCC optimize(3)
#include<bits/stdc++.h>
using namespace std;
const int BBB=1000;
int n,m,BLK[200100],ra[200100],c[200100],d[200100],lp[1000],lim;
vector<pair<int,int> >v;
inline int CALC(int l,int r,int L,int R){
register int res=0;
for(auto x:v){
res-=(x.first>=L)&&(x.first<R)&&(c[x.first]>=l)&&(c[x.first]<r);
res-=(x.second>=L)&&(x.second<R)&&(c[x.second]>=l)&&(c[x.second]<r);
swap(c[x.first],c[x.second]);
res+=(x.first>=L)&&(x.first<R)&&(c[x.first]>=l)&&(c[x.first]<r);
res+=(x.second>=L)&&(x.second<R)&&(c[x.second]>=l)&&(c[x.second]<r);
}
for(int i=v.size()-1;i>=0;i--)swap(c[v[i].first],c[v[i].second]);
if(BLK[L]==BLK[R]){
for(register int i=L;i<R;i++)res+=(c[i]>=l&&c[i]<r);
return res;
}
for(register int i=L;i<lp[BLK[L]+1];i++)res+=(c[i]>=l&&c[i]<r);
for(register int i=lp[BLK[R]];i<R;i++)res+=(c[i]>=l&&c[i]<r);
for(register int i=BLK[L]+1;i<BLK[R];i++)res+=lower_bound(d+lp[i],d+lp[i+1],r)-lower_bound(d+lp[i],d+lp[i+1],l);
return res;
}
void init(){
for(auto x:v)swap(c[x.first],c[x.second]);
v.clear();
for(int i=0;i<n;i++)d[i]=c[i];
for(int i=0;i<lim;i++)sort(d+lp[i],d+lp[i+1]);
}
inline void read(int &x){
x=0;
register char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
inline int read(){
register int x=0;
register char c=getchar();
while(c>'9'||c<'0')c=getchar();
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
return x;
}
inline void print(int x){
if(x<=9)putchar('0'+x);
else print(x/10),putchar('0'+x%10);
}
int main(){
read(n),read(m);
for(register int i=0;i<n;i++)ra[read()]=i,BLK[i]=i/BBB;
for(register int i=0;i<n;i++)c[i]=ra[read()];
lim=BLK[n-1]+1;
for(register int i=0;i<lim;i++)lp[i]=i*BBB;
lp[lim]=n,BLK[n]=lim;
init();
for(register int i=1,t1,t2,t3,t4,t5;i<=m;i++){
read(t1);
if(t1==1)read(t2),read(t3),read(t4),read(t5),t2--,t4--,print(CALC(t2,t3,t4,t5)),putchar('\n');
else read(t2),read(t3),t2--,t3--,v.push_back(make_pair(t2,t3));
if(v.size()>=BBB)init();
}
return 0;
}