腾讯2020 逆序对(堆排,思维)

12 篇文章 0 订阅
1 篇文章 0 订阅

题目大意:

现在有2^n个数,我们总共有m次操作,每次操作我们可以让每2^k(k<=n)个数进行一次反转,现在问我们,每进行一次操作逆序对的个数是多少。

解题思路:

这里,我们需要发现,由于是每2^k进行一次反转,所以对2^(k+1)个数的逆序对是没影响的。具体如图:

假设我们需要翻转绿色内的数字,那么红色圈圈内的跨越绿色圈圈的逆序对是没有任何影响的。

那么,我们从这里出发,考虑维护2^i个数中每的2^(i-1)个数之间的逆序对个数。翻转时候产生的贡献就是:

                                                                                     \sum_{i=1}^{(n)} cnt[i]

其中cnt[i]表示2^i内的2^(i-1)个数之间的逆序对个数。

比如我们在i=n时候,维护的就是上面红色箭头产生的逆序对个数。

另外,我们需要维护2^i个数的顺序对个数,以便反转时候用顺序对的个数替换成为逆序对的个数。

本题的难点:

首先,我们需要有一定的递归的思维考虑怎么算结果,例如这里,我们每次结果都是由2^i内的逆序对求和得出。

其次,我们需要求怎么求逆序对,这需要用到归并排序。

最后,关于敲代码的难点,在归并排序时候有比较多的指针,++顺序容易搞混。每次数组在填值的时候,mv[poi++]的用法可以学一下。

#include <bits/stdc++.h>
#define OPEN 0
#define int long long 
using namespace std;
vector<int> arrmv;
vector<int> cnt, max_c;
void merge_sort(int pos, int sz) {
	if (sz == 0)return; //递归的终点
	int nsz = (1 << (sz - 1));    //区间从中间切开
	int m = pos + nsz;             //另一边merge sort的起点
	merge_sort(pos, sz - 1);        
	merge_sort(m, sz - 1);
	vector<int> tmp((1 << sz), 0);  //暂存merge sort结果
	int l1 = 0, l2 = 0;
	int tpoi = 0;
	while (l1<nsz && l2<nsz) {
		if (arrmv[pos + l1]<=arrmv[m + l2])tmp[tpoi++] = arrmv[pos + l1], l1++;
		else tmp[tpoi++] = arrmv[m + l2], cnt[sz] += m - (l1 + pos), l2++;  //cnt[sz]表示2^sz 下的逆序
        //对有多少个.
	}
    
	while (l1<nsz)
	{
		tmp[tpoi++] = arrmv[pos + l1];
		l1++;
	}
	while (l2 < nsz) {
		tmp[tpoi++] = arrmv[m + l2];
		l2++;
	}
    //完成tmp的填充
    
	l1 = 0; l2 = 0;
	while (l1<nsz && l2<nsz) {
		if (arrmv[pos + l1]<arrmv[m + l2])l1++;
		else if (arrmv[pos + l1]>arrmv[m + l2])l2++;
		else {
			int cnt1 = 0, cnt2 = 0;
			while (l1<nsz && arrmv[pos + l1] == arrmv[m + l2])cnt1++, l1++;
			while (l2<nsz && arrmv[pos + l1 - 1] == arrmv[m + l2])cnt2++, l2++;
			max_c[sz] -= cnt1*cnt2;     //我们找到了左右区间分别有cnt1,cnt2个相同的数,它们
            //在反转区间并不会产生影响,在这里我们需要减掉.那么max_c[sz]剩下的对数分别表示顺序对和
            //逆序对的个数.
		}
	}
	for (int i = 0; i<tmp.size(); i++)arrmv[pos + i] = tmp[i];  //排序结果需要整合给上一层使用
}
int32_t main() {
#if OPEN 
	freopen("vsin.txt", "r", stdin);
#endif
	int n; cin >> n;
	int tot = 1 << n;
	arrmv.assign(tot, 0);
	cnt.assign(n + 1, 0); max_c.assign(n + 1, 0);

	for (int i = 0; i<tot; i++) {
		cin >> arrmv[i];
	}
	for (int i = 1; i <= n; i++)max_c[i] = 1ll << (n + i - 2ll);    //2^i下的总共对数
    //包含顺序对,逆序对以及两两相等对.
    //注意移位时候的1ll表示long long 型
	merge_sort(0, n);
	int nu; cin >> nu;
	for (int ii = 0; ii<nu; ii++) {
		int sz; cin >> sz;
		int sum = 0;
		for (int i = 1; i <= n; i++) { 
			if (i <= sz)cnt[i] = max_c[i] - cnt[i]; //受影响的区间需要把顺序对和逆序对 对调
			sum += cnt[i];
		}
        assert(sum>=0);
		cout << sum << endl;
	}
	return 0;
}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值