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

Problem 1. rotinv
Input file: rotinv.in
Output file: rotinv.out
Time limit: 2 seconds
Memory limit: 256 MB
如果你有一个长度为n 的序列:
a1; a2; a3; : : : ; an
那么它的一个逆序对是一个二元组:< i; j > 满足i < j 且ai > aj,其中i; j 2 [1; n]。
我们称一个序列所包含的逆序对的个数为这个序列的逆序对数。
那么问题来了:
我给出一个长度为n 的序列,需要你计算:
a1; a2 : : : an-?1; an
a2; a3 : : : an; a1
a3; a4 : : : a1; a2
an; a1 : : : an-?2; an-?1
这n 个序列的逆序对之和。
Input
输入文件包含2 行:
第1 行1 个整数:n,表示给定序列的长度。
第2 行n 个整数:a1 a2 : : : an,表示初始序列。
Output
输出n 个序列的逆序对的和。
Sample
rotinv.in                   rotinv.out

2  2  3                       6

以上样例中,3 个序列分别是:2 2 3,2 3 2,3 2 2,分别有0,1,2 个逆序对,所以和为6。
rotinv.in                  rotinv.out
3  

1  1  1                       0

以上样例中,3 个序列都是:1 1 1,逆序对数为0,所以答案为0。
Note
• 对于30% 的数据,1<=n<=300
• 对于60% 的数据,1<=n<=5000
• 对于100% 的数据,1<=n<=10^6,1<=ai<=n


 

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

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

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~

转载于:https://www.cnblogs.com/yyys-/p/10561768.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值