HDU - 1394线段树

因为给定的序列的数是0~n-1不重复的,所以可以边读入数字,边单点更新sum[k]

线段树表示的是:sum[k]对于[l,r]的数字出现的次数和,(即l<=x<=r,x出现的次数;)

题目要求逆序数,即对于i<j,a[i]>a[j]的数的个数和,要求第一次的话是,对于输入的数字x,求他的前面比他大的数字的个数和,再单点为x更新sum[k]++;

求出最开始的序列和之后,可以在这个的基础上继续求将1~n-1个数字放到序列尾部的逆序和,可以这样想:

比如第一次将a1放到了序列的尾部

那么凡是序列中比a1小的数字的逆序和就要减1,故需要在原序列的逆序和的基础上减去a1,而对于a1来说,比他小的数字个数就是n-a[1]-1;故这一次的逆序和为sum-a1+n-a1-1;

以此类推;

可以手推几步就清楚啦;

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const double epos=1e-8;
const int maxn=5e3+3;
int sum[maxn<<2|1];
void erise(int id,int l,int r,int k){
    if(l==r){
        ++sum[k];
        return ;
    }
    int mid=(l+r)>>1;
    if(id<=mid)
        erise(id,l,mid,k<<1);
    else
        erise(id,mid+1,r,k<<1|1);
    sum[k]=sum[k<<1]+sum[k<<1|1];
}

int myfind(int L,int R,int l,int r,int k){
    if(l>=L&&r<=R){
        return sum[k];
    }
    int res=0;
    int mid=(l+r)>>1;
    if(L<=mid)
        res+=myfind(L,R,l,mid,k<<1);
    if(R>mid)
        res+=myfind(L,R,mid+1,r,k<<1|1);
    return res;
}


int a[maxn];

int main(){
    int n;
    while(scanf("%d",&n)!=EOF){
        memset(sum,0,sizeof(sum));
        int res=0;
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
            res+=myfind(a[i]+1,n-1,0,n-1,1);
            erise(a[i],0,n-1,1);
        }
        //printf("%d\n",res);
        int minn=res;
        for(int i=0;i<n;i++){
            res+=n-a[i]-1-a[i];
            minn=min(minn,res);
        }
        printf("%d\n",minn);

    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/chenyume/article/details/84869856