[BZOJ3289]Mato的文件管理

题目大意

给你\(n\)个数,\(m\)个询问

每次询问一段区间\([l,r]\),可以进行交换相邻两个元素的操作,使这段区间从小到大排序的最小操作次数

\(n,m\leq 50000\),元素值在\(1000000\)内,没有重复元素

解题思路

这题第一感觉,好像和逆序对有关?

那么给出性质:

使区间[l,r]按题意方式从小到大排序的最小操作次数等价于区间[l,r]的逆序对总数

这是一个很强的结论了

尝试给出证明:

首先证明操作次数的下限是逆序对个数

很显然,每次操作(交换相邻两数)能且至多消除一个逆序对

因此下限就是逆序对个数

然后我们证明每次操作都可以消除一个逆序对

同样很显然,假设不存在可以消除的逆序对,这意味着这个序列是有序的!

同时,如果序列不是有序的,那么必定存在一对相邻的逆序对可以消除

于是我们(类似贪心的)取到了最优方案

维护

于是这题就变成求区间逆序对个数了

那么考虑用莫队和权值树状数组求解

所谓权值树状数组,就是维护对应权值出现次数
第i位上的值代表i的出现次数
那么k的前缀和的含义就是不大于k的数一共有几个

莫队最核心的就是得到指针移动对答案的影响

我们考虑在末尾增加一个元素的情况:

那个元素贡献的逆序对个数就是前面比它大的元素个数

看样子需要两个权值树状数组,一个维护前缀和,一个后缀和?

其实不用,因为比A大的元素个数就是总数-不大于A的元素个数

这个可以直接用权值树状数组维护前缀和

复杂度分析,\(Blo=\sqrt{n}\)最优

复杂度\(O(n\sqrt{n}logn)\)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

const int Base=230;
int Blo[70000];

int n,q;

namespace tarr{
    int T[100000];
    void clear(){memset(T,0,sizeof(T));}
    inline int lowbit(int k){return k&-k;}
    void add(int K,int v){
        while (K<=n){
            T[K]+=v;
            K+=lowbit(K);
        }
    }
    int query(int K){
        int ret=0;
        while (K){
            ret+=T[K];
            K-=lowbit(K);
        }
        return ret;
    }
}    

struct query{
    int x,y;
    int id;
}Q[100000];
bool cmp_q(query X,query Y){return Blo[X.x]<Blo[Y.x]||(Blo[X.x]==Blo[Y.x]&&X.y<Y.y);}

struct val{
    int V,id;
}V[100000];
bool cmp_v(val A,val B){return A.V<B.V;}

int U[100000],valcnt;
int L,R,val;
int ans[100000];

int main(){
    tarr::clear();
    for (int i=0;i<=50000/Base;i++)
        for (int j=1;j<=Base;j++)
            Blo[i*Base+j]=i;
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&V[i].V),V[i].id=i;
    std::sort(V+1,V+n+1,cmp_v);
    for (int i=1;i<=n;i++){
        if (V[i].V!=V[i-1].V) valcnt++;
        U[V[i].id]=valcnt;
    }
    scanf("%d",&q);
    for (int i=1;i<=q;i++) 
        scanf("%d%d",&Q[i].x,&Q[i].y),Q[i].id=i;
    std::sort(Q+1,Q+q+1,cmp_q);
    L=R=1;tarr::add(U[1],1);
    for (int i=1;i<=q;i++){
        while (R<Q[i].y){
            R++;
            val+=R-L-tarr::query(U[R]-1);
            tarr::add(U[R],1);
        }
        while (L>Q[i].x){
            L--;
            val+=tarr::query(U[L]-1);
            tarr::add(U[L],1);
        }
        while (R>Q[i].y){
            val-=R-L-tarr::query(U[R]-1);
            tarr::add(U[R],-1);
            R--;
        }
        while (L<Q[i].x){
            val-=tarr::query(U[L]-1);
            tarr::add(U[L],-1);
            L++;
        }
        ans[Q[i].id]=val;
    }
    for (int i=1;i<=q;i++) printf("%d\n",ans[i]);
}

转载于:https://www.cnblogs.com/ytxytx/p/9820960.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值