CF785E Anton and Permutation
题目大意
大体题意如下:
给定一个长度为 \(n\) 的排列,初始为 \(1,2,\cdots,n\),对其进行 \(k\) 次操作,每次操作给定两个位置 \(l\) 和 \(r\),求交换 \(a\left[l\right]\) 和 \(a\left[r\right]\) 后在区间 \(\left[ l ,r\right]\) 内的逆序对个数变化
数据大小:\(1\le n\le 200000,1\le q\le50000\)
时空限制: \(4s\) \(512MB\)
思路
首先考虑每次交换后如何求的逆序对
设:
\(x_1\) 表示在区间 \(\left[l,r\right]\) 内比 \(a_l\) 小的 \(a_i\) 的个数
\(x_2\) 表示在区间 \(\left[l,r\right]\) 内比 \(a_r\) 小的 \(a_i\) 的个数
\(y_1\) 表示在区间 \(\left[l,r\right]\) 内比 \(a_l\) 大的 \(a_i\) 的个数
\(y_2\) 表示在区间 \(\left[l,r\right]\) 内比 \(a_r\) 大的 \(a_i\) 的个数
\(sum\) 表示在区间 \(\left[l+1,r-1\right]\) 内逆序对的个数,显然交换 \(a_l\) 和 \(a_r\) 不会影响 \(sum\) 的大小
\(ans\) 表示交换前区间 \(\left[l,r\right]\) 内的逆序对个数
\(ans'\) 表示交换后区间 \(\left[l,r\right]\) 内的逆序对个数
则有
显然也有
用上述式子带入 \(ans\) 和 \(ans'\) 就有
用 \(ans\) 向 \(ans'\) 转移就有
这就是最后状态转移的方程,由此就可以每次通过变换前的逆序对数量和序列中的大小关系来求的变换后逆序对的数量
实现
分块的时间复杂度为 \(O(q\sqrt{n})\),根据本题数据大小计算可以通过
大体的模板不变,问题转化成给定一个数 \(d\) ,如何比较高效地统计在区间 \(\left[l,r\right]\)内比\(d\) 小的数的数量。
直接遍历统计的时间复杂度为 \(O(n)\),\(q\) 次询问复杂度会直接增加到 \(O(nq)\)。
对 \(\left[l,r\right]\) 进行排序的时间复杂度为 \(O(n\log{n})\),\(q\) 次询问复杂度会增加到 \(O(nq\log n)\),比直接遍历还要差。
可以发现每次交换两个数时,类似于插入排序,可以对于每个块另开一个 vector 数组,初始化是保证序列上升,每次通过二分对 \(a\left[l\right]\) 和 \(a\left[r\right]\) 所在的块进行插入和删除,这样就可以一直保证每块的序列有序,时间复杂度 \(O(\log \sqrt{n})\)。
统计时对于散块进行暴力统计,对整块二分和散块暴力统计的时间复杂度为 \(O(\sqrt{n}\log \sqrt{n}+\sqrt{n}))\)。
具体细节请看代码
代码
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
const int N=2e5+10,M=450;
typedef long long ll;
int n,q,len;
int a[N],block[N];
ll sum[M],res;
vector<int>aa[M];
int get(int i)
{
return block[i]=(i-1)/len+1;
}
void change(int l,int r)
{
if(block[l]!=block[r])
{
aa[block[l]].erase(lower_bound(aa[block[l]].begin(),aa[block[l]].end(),a[l]));
aa[block[l]].insert(upper_bound(aa[block[l]].begin(),aa[block[l]].end(),a[r]),a[r]);
aa[block[r]].erase(lower_bound(aa[block[r]].begin(),aa[block[r]].end(),a[r]));
aa[block[r]].insert(upper_bound(aa[block[r]].begin(),aa[block[r]].end(),a[l]),a[l]);
}
swap(a[l],a[r]);
}
ll query(int l,int r,int d)
{
if(l>r)return 0;
ll res=0;
if(block[l]==block[r])
{//在一块中
for(int i=l;i<=r;i++)res+=(a[i]<d);
return res;
}//不在一块中
for(int i=l;i<=len*block[l];i++)res+=(a[i]<d);//处理左侧散块
for(int i=len*(block[r]-1)+1;i<=r;i++)res+=(a[i]<d);//处理右侧散块
for(int i=block[l]+1;i<=block[r]-1;i++)res+=lower_bound(aa[i].begin(),aa[i].end(),d)-aa[i].begin();
//处理中间的整块,每次直接lower_bound出比d少的数量,由于区间中没有d,因此统计的所有数一定都是符合a[i]<d的
return res;
}
int main()
{
ios::sync_with_stdio(0);
cin>>n>>q;
len=sqrt(n);
for(int i=1;i<=n;i++)
{
a[i]=i;//每个位置初始化
aa[get(i)].push_back(a[i]);//初始化每块的初始长度,保证是单调上升序列
}
while(q--)
{
int l,r;
cin>>l>>r;
if(l>r)swap(l,r);
if(l==r)
{
cout<<res<<"\n";
continue;
}
res+=2*(query(l+1,r-1,a[r])-query(l+1,r-1,a[l]));
if(a[l]<a[r])res++;
else res--;
change(l,r);
cout<<res<<"\n";
}
return 0;
}