题目大意:
现在有2^n个数,我们总共有m次操作,每次操作我们可以让每2^k(k<=n)个数进行一次反转,现在问我们,每进行一次操作逆序对的个数是多少。
解题思路:
这里,我们需要发现,由于是每2^k进行一次反转,所以对2^(k+1)个数的逆序对是没影响的。具体如图:
假设我们需要翻转绿色内的数字,那么红色圈圈内的跨越绿色圈圈的逆序对是没有任何影响的。
那么,我们从这里出发,考虑维护2^i个数中每的2^(i-1)个数之间的逆序对个数。翻转时候产生的贡献就是:
其中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;
}