字母交换(树状数组+归并排序)

时间限制: 1 Sec 内存限制: 128 MB
[提交] [状态]
题目描述
某正教授级特级教师获得了一段古老的文字,全部由 26 个大写英文字母组成。他产生了一个疯狂的想法,即想把这段文字中所有字母按 A 到 Z 的顺序排序,即所有 A 放在开头,然后跟着所有 B,再是所有 C,最后是所有 Z。比如原
字符串为“HELLOWORLD”,排序后应变为“DEHLLLOORW”。但是特教毕竟领着国务院的特殊津贴,于是他还有一个要求,即排序时每次只能交换相邻两个字母。现在他想知道最少交换多少次能完成排序?
输入
仅一行,包含一个仅含大写字母的长度为 L 的字符串(注意 L 不输入)。
输出
共一行,包含一个整数表示最少交换次数。
样例输入 Copy
【样例1】

LSDSL

【样例2】

HELLOWORLD

样例输出 Copy
【样例1】

4

【样例2】

16

提示
对于50%的数据,1≤L≤2000;
对于100%的数据,1≤L≤2×10^6
题意即求冒泡排序的交换次数,由于冒泡排序得到的最终序列是确定的,因此对于一个给定的序列,交换次数也就唯一确定。根据冒泡排序的原理,当相邻的两个元素中前一个大于后一个时交换,因此只需求整个序列中的逆序对。
求逆序对的题目昨天做到过,是用树状数组求解,可是没有考虑全面就写代码,交了好几次才过。
注意几个问题:
1.每个字母不一定只出现一次,在排序的时候不仅要按字母升序排序,还要在字母相同的情况下按原来标记的索引升序排序。
2.使用sort函数排序会超时(1055ms)(?),网上说在oj上qsort快于sort。(?)
参考资料:1.树状数组 数据结构详解与模板(可能是最详细的了)
2.树状数组求逆序对 (超超详细讲解)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char str[2000005];
typedef long long int ll;
typedef struct let
{
    char al;
    int index;
}l;
l letter[2000005];
ll tree[2000005]={0};
int len;
int lowbit(int x)
{
    return x&(-x);
}
void update(int pos)
{
    int i;
    for(i=pos;i<=len;i+=lowbit(i))tree[i]++;
}
ll getsum(int pos)
{
    ll ans=0;
    int i;
    for(i=pos;i>=1;i-=lowbit(i))ans+=tree[i];
    return ans;
}
int cmp(const void*a,const void*b)//自定义cmp函数
{
    l*xx=(l*)a;
    l*yy=(l*)b;
    if(xx->al!=yy->al)return xx->al-yy->al;
    else return xx->index-yy->index;
}
int main()
{
    int i;
    ll cnt=0;
    scanf("%s",str+1);
    len=strlen(str+1);
    for(i=1;i<=len;i++)
    {
        letter[i].al=str[i];
        letter[i].index=i;
    }
    qsort(letter+1,len,sizeof(letter[1]),cmp);//按字母升序、索引升序排列
    for(i=1;i<=len;i++)
    {
        update(letter[i].index);
        cnt+=(ll)i-getsum(letter[i].index);
    }
    printf("%lld",cnt);
    return 0;
}
/**************************************************************
    Language: C++
    Result: 正确
    Time:602 ms
    Memory:49952 kb
****************************************************************/

也可以借助归并排序求解。
归并排序是一种分治思想下的排序算法:将待排序序列不断划分为多个更小的序列,直至不可划分,然后使每个最小区间内部是有序的,然后再逆着刚才的划分过程不断地将小区间合并为较大的区间,最终逆过程结束后,排序完成。

归并排序算法图解(图片来自百度百科)
求解逆序数在合并过程中进行,随着序列的合并,合并前的子序列都是有序的。在合并过程中,当a[i]>a[j]时才存在逆序数。由于a[i]在左半部分序列,a[j]在右半部分序列,在左半部分序列中,对于所有的i'>i,都有a[i']>a[i]>a[j],因此这部分数均为a[j]的逆序数,个数为mid-i+1,其中mid为左子序列的右端点。
归并排序算法相比树状数组,效率提高了不到一倍,而且内存开销也较小。。。

#include<cstdio>
#include<queue>
#include<algorithm>
#include<stack>
#include<string>
#include<cstring>
#include<vector>
#include<map>
#include<set>
using namespace std;
char seq[2000005];
int a[2000005];
int b[2000005];
int len;
long long int ans=0;
void Merge(int first,int middle,int last)
{
    int i,j,k;
    i=first;
    j=middle+1;
    k=0;
    while(i<=middle&&j<=last)
    {
        if(a[i]<=a[j])b[++k]=a[i++];
        else ans+=middle-i+1,b[++k]=a[j++];
    }
    while(i<=middle)b[++k]=a[i++];
    while(j<=last)b[++k]=a[j++];
    for(i=1;i<=k;i++)a[first+i-1]=b[i];
}
void MergeSort(int Start,int End)
{
    if(Start<End)
    {
        int Mid=(Start+End)/2;
        MergeSort(Start,Mid);
        MergeSort(Mid+1,End);
        Merge(Start,Mid,End);
    }
}
int main()
{
    scanf("%s",seq+1);
    len=strlen(seq+1);
    for(int i=1;i<=len;i++)a[i]=seq[i]-'A'+1;
    MergeSort(1,len);
    printf("%lld",ans);
    return 0;
}
/**************************************************************
    Language: C++
    Result: 正确
    Time:372 ms
    Memory:18696 kb
****************************************************************/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值