题目链接:Codeforces-785E-Anton and Permutation
当交换a[l]和a[r]时。讨论区间为(l,r),那么
所以问题就转化为如何求一个连续区间
(l,r)
内比给定值val小的元素个数。
考虑分块。且块中元素保持有序,便于二分查询。
1.若l==r,则ans不变。
2.若l和r属于同一个块,可以
O(sqrt(n))
扫描区间即可。
3.若l和r不属于同一个块,先扫描区间
(l,b[l].r]
和
[b[r].l,r)
。然后
O(sqrt(n)∗log(sqrt(n)))
地计算出中间其他块小于
a[l]
或
a[r]
的元素。
最坏情况下询问复杂度应该是
O(q∗sqrt(n)∗log(sqrt(n)))
q=5∗104
,
n=2∗105
, 最坏应该是
2∗108
。可以解决。
这道题可以使用分块查询的原因是每次更新时,最多只需要处理2个块,其他块中的信息能够以 O(log(sqrt(n))) 的复杂度快速获得。因此查询的复杂度从 O(n) 降到了 O(sqrt(n)∗log(sqrt(n))) 。这种算法可以推广到所有具有上述性质的问题。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+7;
int n,q,m;
ll ans;
struct Block
{
int l,r;
vector<int> arr;
};
Block b[maxn];
int bl[maxn];
int a[maxn];
int solve(int l,int r,int val)
{
// 若l,r不在该,则直接使用二分计算出小于val的元素个数。复杂度log(sqrt(n))
int res=0;
for(int i=l+1;i<r;i++)
res+=lower_bound(b[i].arr.begin(),b[i].arr.end(),val)-b[i].arr.begin();
return res;
}
void update(int id, int u,int v)
{
// 更新块中元素,保持有序
b[id].arr.erase(lower_bound(b[id].arr.begin(),b[id].arr.end(),a[u]));
b[id].arr.insert(lower_bound(b[id].arr.begin(),b[id].arr.end(),a[v]),a[v]);
}
void scan(int l, int r, int vall, int valr)
{
// O(n)扫描并更新ans
for(int i=l;i<=r;i++)
{
if(a[i]<valr) ans++;
if(a[i]<vall) ans--;
if(a[i]>vall) ans++;
if(a[i]>valr) ans--;
}
}
int main()
{
scanf("%d%d",&n,&q);
// 分块
int blocklen=n/(int)(sqrt(n)+1)+1;
for(int i=1;i<=n;i++)
{
a[i]=i;
bl[i]=(i-1)/blocklen+1;
if(!b[bl[i]].l) b[bl[i]].l=i;
b[bl[i]].r=i;
b[bl[i]].arr.push_back(i);
}
for(int i=1;b[i].arr.size();i++)
sort(b[i].arr.begin(),b[i].arr.end());
while(q--)
{
int l,r;
scanf("%d%d",&l,&r);
if(l==r)
{
printf("%I64d\n",ans);
continue;
}
if(l>r) swap(l,r);
if(a[l]>a[r]) ans--;
else ans++;
if(bl[l]==bl[r])
scan(l+1,r-1,a[l],a[r]);
else
{
scan(l+1,b[bl[l]].r,a[l],a[r]);
scan(b[bl[r]].l,r-1,a[l],a[r]);
int u=solve(bl[l],bl[r],a[l]);
int v=solve(bl[l],bl[r],a[r]);
ans=ans-2*u+2*v;
update(bl[l],l,r);
update(bl[r],r,l);
}
swap(a[l],a[r]);
printf("%I64d\n",ans);
}
return 0;
}