逆序对--《算法竞赛进阶指南》(Ultra-QuickSort和奇数码问题)

逆序对

设 A 为一个有 n 个数字的有序集 (n>1),其中所有数字各不相同。
如果存在正整数 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],则 <A[i], A[j]> 这个有序对称为 A 的一个逆序对,也称作逆序数。例如,数组(3,1,4,5,2)的逆序对有(3,1),(3,2),(4,2),(5,2),共4个。

题目 Ultra-QuickSort

给定一个长度为n(n<=5*10^5)的序列a,如果只允许进行比较和交换相邻两个数的操作,求至少需要多少次交换才能把a从小到大排序。

输入

The input contains several test cases. Every test case begins with a line that contains a single integer n < 500,000 – the length of the input sequence. Each of the the following n lines contains a single integer 0 ≤ a[i] ≤ 999,999,999, the i-th input sequence element. Input is terminated by a sequence of length n = 0. This sequence must not be processed.

输出

For every input sequence, your program prints a single line containing an integer number op, the minimum number of swap operations necessary to sort the given input sequence.

样例输入
5
9
1
0
5
4
3
1
2
3
0

样例输出
6
0

思路:

  • 可以使用归并排序求出一个长度为n的序列中逆序对的个数。归并排序中每次把序列二分,递归对左右两半排序,然后合并两个有序序列。(这也是merge1的算法思想)
  • 合并两个有序序列s[a~b] 与 s[b+1~c]可以采用两个指针i与j分别对两者进行扫描的方式,不断比较两个指针所指向s[ i ]和s[ j ]的大小,将小的那个加入到排序的结果数组中。若小的那个是s[ j ],则s[a~b]都比s[ j ]大,它们都会与s[ j ]构成逆序对,可以顺便统计到答案中。(这也是merge的思想哦~)(b也就是mid,a和c是上下界)
#include <bits/stdc++.h>
 using namespace std;
int s[500000],tt[500000];//这里数组开的太小跑得会比较慢 
long long sum;
 void merge(int a,int b,int c)//合并s[a~b]与s[b+1~c] 
 {
 	int i=a,j=b+1,k=a;
 	while(i<=b&&j<=c)
 	{
 		if(s[i]<s[j])
 		{
 			tt[k++]=s[i++];//s是待排序数组,tt是临时数组,sum是逆序对个数 
		 }
		else
		{
			sum+=j-k;
			tt[k++]=s[j++];
		}
	 }
	while(i<=b)
        tt[k++]=s[i++];
    while(j<=c)
        tt[k++]=s[j++];
    for(i=a; i<=c; ++i)
        s[i]=tt[i];
 }
 void merge1(int a,int b)
{
    int mid;
    if(a<b)
    {
        mid=(a+b)/2;
        merge1(a,mid);
        merge1(mid+1,b);
        merge(a,mid,b);
    }
}
 int main()
 {
 	int n;
 	while(1==scanf("%d",&n)&&n>0)
 	{
 		sum=0;
 		for(int i=0;i<n;i++)
 		{
 			scanf("%d",&s[i]);
		 }
		merge1(0,n-1);
		printf("%lld\n",sum);
	 }
	 return 0;
 }

题目 奇数码问题

你一定玩过八数码游戏,它实际上是在一个3x3的网格中进行的,1个空格和1~8这8个数字恰好不重不漏地分布在这33的网格中。
例如:
5 2 8
1 3 _
4 6 7
在游戏过程中,可以把空格与其上、下、左、右四个方向之一的数字交换(如果存在)。
例如在上例中,空格可与左、上、下面的数字交换,分别变成:
5 2 8 5 2 _ 5 2 8
1 _ 3 1 3 8 1 3 7
4 6 7 4 6 7 4 6 _
奇数码游戏是它的一个扩展,在一个n
n的网格中进行,其中n为奇数,1个空格和1~nxn-1这nxn-1个数恰好不重不漏地分布在nxn的网格中。
空格移动的规则与八数码游戏相同,实际上,八数码就是一个n=3的奇数码游戏。
现在给定两个奇数码游戏的局面,请判断是否存在一种移动空格的方式,使得其中一个局面可以变化到另一个局面。

输入

多组数据,对于每组数据:
第1行一个整数n,n<500,n为奇数。
接下来n行每行n个整数,表示第一个局面。
接下来n行每行n个整数,表示第二个局面。
局面中每个整数都是0~nxn-1之一,其中用0代表空格,其余数值与奇数码游戏中的意义相同,保证这些整数的分布不重不漏。

输出

对于每组数据,若两个局面可达,输出TAK,否则输出NIE。

样例输入

3
1 2 3
0 4 6
7 5 8
1 2 3
4 5 6
7 8 0
1
0
0

样例输出

TAK
TAK

思路:
和上面一道题很相似。只不过这里是通过判断两个状态的逆序对个数的奇偶性是否相同。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 500000;
int s[N], tt[N];
ll sum1, sum2;
int n;
void merge(int a, int b, int c) {//这里合并成一个函数也没问题啦
    if (a>= c)return;
    if (a + 1 == c) {
        if (s[a] > s[c]) {
            swap(s[a], s[c]);
            sum2++;
        }
        return;
    }
    merge(a, (a + b) >> 1, b);
    merge(b + 1, (b + 1 + c) >> 1, c);
    int i = a, j =b + 1;
    for (int k = a; k <= c; k++) {
        if (j > c || (i <=b&& s[i] <= s[j]))tt[k] = s[i++];
        else {
            tt[k] = s[j++];
           sum2+= b - i + 1;
        }
    }
    for (int k = a; k <= c; k++)
        s[k] = tt[k];
}

int main() {
    while (cin >> n) {
        sum2 = 0;
        for (int i = 1, j = 0; i <= n * n; i++) {
            int x;
            scanf("%d", &x);
            if (x)
			s[++j] = x;
        }
        merge(1, (n * n) >> 1, n * n - 1);
        sum1 = sum2, sum2 = 0;
        for (int i = 1, j = 0; i <= n * n; i++) {
            int x;
            scanf("%d", &x);
            if (x)
			s[++j] = x;
        }
        merge(1, (n * n) >> 1, n * n - 1);
        if ((sum1 % 2) == (sum2 % 2))printf("TAK\n");
        else printf("NIE\n");
    }
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值