Codeforces-785E-Anton and Permutation(分块区间查询,动态查询[l,r]内小于某个值的元素个数)

题目链接:Codeforces-785E-Anton and Permutation

当交换a[l]和a[r]时。讨论区间为(l,r),那么

ans=ans+a[r]a[l]+a[l]a[r]+(a[l]>a[r]?1:1)

所以问题就转化为如何求一个连续区间 (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(qsqrt(n)log(sqrt(n)))
q=5104 , n=2105 , 最坏应该是 2108 。可以解决。

这道题可以使用分块查询的原因是每次更新时,最多只需要处理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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值