CF240 DIV1 C--归并排序求逆序

10 篇文章 0 订阅
Mashmokh and Reverse Operation
time limit per test
4 seconds
memory limit per test
512 megabytes
input
standard input
output
standard output

Mashmokh's boss, Bimokh, didn't like Mashmokh. So he fired him. Mashmokh decided to go to university and participate in ACM instead of finding a new job. He wants to become a member of Bamokh's team. In order to join he was given some programming tasks and one week to solve them. Mashmokh is not a very experienced programmer. Actually he is not a programmer at all. So he wasn't able to solve them. That's why he asked you to help him with these tasks. One of these tasks is the following.

You have an array a of length 2n and m queries on it. The i-th query is described by an integer qi. In order to perform the i-th query you must:

  • split the array into 2n - qi parts, where each part is a subarray consisting of 2qi numbers; the j-th subarray (1 ≤ j ≤ 2n - qi) should contain the elements a[(j - 1)·2qi + 1], a[(j - 1)·2qi + 2], ..., a[(j - 1)·2qi + 2qi];
  • reverse each of the subarrays;
  • join them into a single array in the same order (this array becomes new array a);
  • output the number of inversions in the new a.

Given initial array a and all the queries. Answer all the queries. Please, note that the changes from some query is saved for further queries.

Input

The first line of input contains a single integer n (0 ≤ n ≤ 20).

The second line of input contains 2n space-separated integers a[1], a[2], ..., a[2n] (1 ≤ a[i] ≤ 109), the initial array.

The third line of input contains a single integer m (1 ≤ m ≤ 106).

The fourth line of input contains m space-separated integers q1, q2, ..., qm (0 ≤ qi ≤ n), the queries.

Note: since the size of the input and output could be very large, don't use slow output techniques in your language. For example, do not use input and output streams (cin, cout) in C++.

Output

Output m lines. In the i-th line print the answer (the number of inversions) for the i-th query.

Sample test(s)
input
2
2 1 4 3
4
1 2 0 2
output
0
6
6
0
input
1
1 2
3
0 1 1
output
0
1
0
Note

If we reverse an array x[1], x[2], ..., x[n] it becomes new array y[1], y[2], ..., y[n], where y[i] = x[n - i + 1] for each i.

The number of inversions of an array x[1], x[2], ..., x[n] is the number of pairs of indices i, j such that: i < j and x[i] > x[j].


转自:http://blog.csdn.net/acmmmm/article/details/23189375

先复制一个思路:

解法:2^n个数,可以联想到建立一棵二叉树的东西,比如  2,1,4,3就可以建成下面这样

                                                    [2,1,4,3]                        level 2

                                                   /               \

                                              [2,1]              [4,3]                  level 1

                                           /        \              /     \

                                        [2]         [1]        [4]    [3]              level 0

然后算某个区间的逆序数的时候[2,1,4,3]实际上就是算[2,1],[4,3]的逆序数再加上[2,1],[4,3]之间的逆序数,思路和归并排序是一样的。

然后我们看下每次翻转,假如我们分成2^k份,我们会发现,对于level k+1层以上的逆序数是不会改变的,但是level k~level 0的逆序数对会翻转,我们只需要知道level k~level 0各个区间翻转后的逆序数就可以了。 

题意:

给定 2^n 长的序列 

下面que个询问,对于每个询问 u,

1、先把 序列分段分成: [0, 2^u-1], [2^u, 2^(u+1)-1],······

2、再把每段都翻转一下。 

3、输出此时序列的逆序数。

思路:首先我们要得到:

对于一个序列[l,r) 的翻转 =

1、 将该序列的[l,mid), [mid,r)}交换 

 2、[l,mid/2),[mid/2,mid)交换 + [mid,mid+mid/2),[mid+mid/2,r) 交换

 3、 如此递归下去

例如:12345678-> 87654321

等价于 1234 | 5678 -> 5678 | 1234 -> 7856 | 3412 -> 8765 | 4321

于是把序列分层,得到n层

对于每一层[l, r] 的逆序数对分为:

1、两个数字都在[l,mid) 内的 +两个数字都在 [mid,r]内的

2、一个数字在[l,mid),一个数字在[mid,r]

3、若区间内只有2个数则只有第二种情况,第一种情况答案为0.

所以累加上每层的区间之间的逆序数即为整个序列的逆序数

而交换一下区间就是 2、中结果变成 : 一个数字在[mid,r] ,一个数字在[l,mid )

即每层的2有2种答案。

预处理出每层的2个答案,累加即可。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>

using namespace std;
#define N 20
#define ll __int64
ll a[1 << N], n, m;
//一个序列[l,r) 的翻转 = 将该序列的{[l,mid), [mid,r)} 交换 + ({[l,mid/2),[mid/2,mid)} + {[mid,mid+mid/2),[mid+mid/2,r)} ) + 如此递归下去

ll sum[N + 1][2]; 
//sum[deep]表示 求和每段[l,r]的:[l,mid)和[mid,r) 之间的逆序数
void dfs(ll l, ll r, ll deep){
	if(l+1 >= r)return ;
	ll mid = (l+r)/2;
	dfs(l, mid, deep-1);
	dfs(mid, r, deep-1);
	ll cnt = 0;
	for(ll i = l; i < mid; i++)
		cnt += lower_bound(a+mid, a+r, a[i]) - (a+mid);
	sum[deep][0] += cnt; s

	cnt = 0;
	for(ll i = mid; i < r; i++)
		cnt += lower_bound(a+l, a+mid, a[i]) - (a+l);
	sum[deep][1] += cnt;
	inplace_merge(a+l, a+mid, a+r);
}
int main(){
	ll n, i, que, u;
	ll er[30];er[0] = 1;
	for(i=1;i<30;i++)er[i] = er[i-1]*2;
	while(~scanf("%I64d",&n)){
		memset(sum, 0, sizeof sum);
		for(i = 0; i < er[n]; i++)scanf("%I64d",&a[i]);

		dfs(0, er[n], n);
		scanf("%I64d",&que);
		while(que--)
		{
			scanf("%I64d",&u);
			ll ans = 0;

			while(u--)swap(sum[u+1][0], sum[u+1][1]);
			for(i=0; i <= n; i++) ans += sum[i][0];

			printf("%I64d\n",ans);
		}
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
归并排序是一种常用的排序算法,同时也可以用来数。在C语言中,可以通过实现归并排序算法来数。 首先,我们需要定义一个全局变量来统计数的数量。然后,我们可以使用归并排序的思想来进行排序并计算数。 归并排序的核心思想是将一个数组分成两个子数组,分别对子数组进行排序,然后再将两个排好的子数组合并成一个有的数组。在合并的过程中,我们可以统计数的数量。 具体实现归并排序数的步骤如下: 1. 定义一个全局变量count,用于统计数的数量。 2. 创建一个辅助数组temp,用于存储排序的结果。 3. 实现一个递归函数mergeSort,用于对数组进行归并排序。 4. 在mergeSort函数中,首先判断数组的长度是否大于1,如果小于等于1,则直接返回。 5. 将数组分成两个子数组,分别递归调用mergeSort函数进行排序。 6. 在合并两个子数组时,比较两个子数组的元素大小,并将较小的元素放入temp数组中。如果存在,则将数的数量count加上左子数组中剩余的元素个数。 7. 将剩余的元素依次放入temp数组中。 8. 将temp数组中的元素复制回原数组。 9. 最后,返回数的数量count。 以下是用C语言实现归并排序数的示例代码: ```c #include <stdio.h> int count = 0; void merge(int arr[], int left, int mid, int right) { int i = left, j = mid + 1, k = 0; int temp[right-left+1]; while (i <= mid && j <= right) { if (arr[i <= arr[j]) { temp[k++] = arr[i++]; } else { temp[k++] = arr[j++]; count += mid - i + 1; // 统计数的数量 } } while (i <= mid) { temp[k++] = arr[i++]; } while (j <= right) { temp[k++] = arr[j++]; } for (i = left, k = 0; i <= right; i++, k++) { arr[i = temp[k]; } } void mergeSort(int arr[], int left, int right) { if (left >= right) { return; } int mid = (left + right) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid + 1, right); merge(arr, left, mid, right); } int main() { int arr[] = {9, 6, 5, 3, 2}; int n = sizeof(arr) / sizeof(arr<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [数(C语言)--归并](https://blog.csdn.net/qq_46182442/article/details/104055951)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* *3* [C语言归并排序数](https://blog.csdn.net/wwxy1995/article/details/82953957)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值