2019.3.16数据结构考试(Problem 1. rotinv)(循环逆序对)

大概意思呢,就是求循环序列的逆序对

题解中有这样的一个思考过程

1.1 30%
O(n3) 暴力:枚举每个循环状态,再暴力计算逆序对数。

#include <cstdio>
#include <iostream>
using namespace std;
const int N = 1000000 + 10;
int n;
int aa[N + N];
int main() {
    scanf( "%d", &n );
    for( int i = 1; i <= n; i++ ) {
        scanf( "%d", aa + i );
        aa[n + i] = aa[i];
    }
    long long ans = 0;
    for( int i = 1; i <= n; i++ ) {
        for( int l = 0; l < n; l++ )
            for( int r = l + 1; r < n; r++ )
                ans += (aa[i+l] > aa[i+r]);
    }
    cout << ans << endl;
}
30分暴力

1.2 60%
法一:

将原序列复制一份接在自己后面,形成一个长度为2n 的序列,先暴力计算[1,n] 的逆序对数,然后将这个区间向右边移动,维护答案。具体来说,就是假如我们算出了[i,j] 中的逆序对数,我们怎么算[i + 1, j + 1] 呢,其实就是加上[i+1, j] 中比a[j +1] 大的数的个数再减去[i+1, j] 中比a[i] 小的数的个数。所以我们可以暴力算那两个数量,连续n 个长度为n 的子区间的逆序数的和就是答案,复杂度O(n2)。
法二:

计算一个长度为n 的序列的逆序对的个数可以通过数据结构(树状数组或线段树)优化到O(nlogn),维护[1,i - 1] 的一个值的分布情况(加入到线段树中),每次查询这些数比a[i] 大的有多少个,再将a[i] 加入到线段树中去。所以计算n 个序列的逆序数只需要O(n2logn).
1.3 100%
将上面的法一和法二结合一下,用线段树优化法一中那个暴力,使得可以O(logn) 查询,最后复杂度O(nlogn).

#include<bits/stdc++.h>
#define N 1000003
#define ll long long
using namespace std;
ll a[N*2],n,c[N*4];
ll lowbit(int n){return n&(-n);}
void update(ll x,ll k)
{
    while(x<=n)
    {
        c[x]+=k;
        x+=lowbit(x);
    }
}
ll query(ll x)
{
    ll ans=0;
    while(x)
    {
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
//树状数组里存的是1~n每个数的个数(其实就是树状数组求逆序对+区间移动的思想)
//对于一个数a[i],可以query它的位置query(a[i])-1就是前面比他小的数的个数,
//那么前面就有i-query[a[i]]个比它小的数 
int main()
{
//    freopen("rotinv.in","r",stdin);
//    freopen("rotinv.out","w",stdout);
    int m;
    ll ans=0,cur=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%I64d",&a[i]);
        a[n+i]=a[i];
    }
    for(int i=1;i<=n;i++)
    {
        ans+=(i-1)-query(a[i]);//i-1是因为这个时候只存了i-1个数,第i个数还没存la 
        update(a[i],1);//将a[i]的个数加1 
    }
    for(int i=n+1;i<=2*n;i++)
    {
        update(a[i-n],-1);//区间移动,a[i-n]已经不在区间内了,
        ans+=(n-1)-query(a[i]);//那么就要把因为a[i-n]存在而失去的贡献加上
        ans-=query(a[i-n]-1);//减去a[i-n]带来的贡献(这里找的是比a[i-n]小的数的个数,比其小才能构成逆序对)
        update(a[i],1);
        cur+=ans;//ans是每个序列的答案,最终的答案是每个序列的ans加起来 
    }
    printf("%I64d",cur);
    
}
/*
5 4
1 3 2 4 2
1 4
2 4
1 3
2 3
*/
AC(树状数组)

当然逆序对还可以用归并排序求,再以类似的思想循环序列:相当于每次把队首踢到队尾,那么与它有关的贡献就会更新

代码很好理解~

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
#define ll long long
int n,sma[N],big[N];
ll tot=0;
void merge(int a[],int l,int r,int mid,int b[])
{
    memcpy(b+l,a+l,sizeof(int)*(r-l+1));
    int i=l,k=l,j=mid+1;
    while(i<=mid&&j<=r) 
    {
        if(b[i]<=b[j])
          a[k++]=b[i++];
         else
          a[k++]=b[j++],tot+=(mid-i+1);
    }
    
    while(i<=mid) a[k++]=b[i++];
    while(j<=r)   a[k++]=b[j++];
}
void merge_sort(int a[],int l,int r,int b[])
{
    if(l<r)
    {
        int mid=(l+r)>>1;
        merge_sort(a,l,mid,b);
        merge_sort(a,mid+1,r,b);
        merge(a,l,r,mid,b);
    }
}
//上面都是归并排序 
int a[N<<1],b[N<<1],c[N<<1];
ll ans=0;
int main()
{
//    freopen("rotinv.in","r",stdin);
//    freopen("rotinv.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
     scanf("%d",&a[i]),c[i]=a[i];
    merge_sort(a,1,n,b);
    //排序后再分别找出序列中比a[i]小,大的个数 
    for(int i=2;i<=n;i++)
    if(a[i]!=a[i-1])
     sma[a[i]]=i-1;
    for(int i=n-1;i>=1;i--)
    if(a[i]!=a[i+1])
     big[a[i]]=n-i;
    for(int i=1;i<=n;i++)//不断的踢出每一个c[i] 
    {
        ans+=tot;
        tot-=sma[c[i]];
        tot+=big[c[i]];
    }
    printf("%I64d\n",ans);
}
归并排序

完成lalala~

posted @ 2019-03-19 22:02  yyys  阅读(325)  评论(1编辑  收藏  举报