这个题目如果不知道怎么用归并排序求逆序对还是很难想的,但是知道用归并排序求逆序对也不是一个很好写的题目。
注意题目是每一个数只出现一次!!!
思路:
整体求考虑每一次操作的影响。
因为每次操作都是2的幂次,那么我们先求出划分成(2^x) 块的每一个块内的逆序对之和,这个可以用归并排序做,所以假设 (dp[x]) 表示每一个块大小是 (2^x) 次方的所有块逆序对之和(注意这个定义是大小是(2^x) 块),那么容易知道,一个块的逆序对+顺序对=(len*(len-1)/2) 这个 (len) 表示块的大小。
接下来考虑如果要求划分成 $2^q $ 块,那么每块大小就是 (2^x = 2^n - 2^q) ,因为每块划分大小是 (2^x) 这样对更大的块和更小的块的影响是不同的,对比这个块更小的块的影响就是:顺序对变成逆序对,逆序对变成顺序对,对比这个块更大的块的影响是:变化的块顺序对逆序对交换,但是块与块直接的顺逆序对数量不变。
最后得出来的式子就是:(f[i]) 表示 (2^i) (temp[i]) 表示这次操作未更新之前的 (dp[i])
(i<=x) : (dp[i] = f[i]*(f[i]-1)/2*f[n-i]-dp[i])
(i>x) :(dp[i] = dp[i]-temp[i-1]+dp[i-1])
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e6+10;
ll a[maxn],temp[maxn],dp[40],f[40];
ll merge(int pos,int l,int r){
if(pos==0){ dp[pos] = 0;return 0;}
ll ans = 0;
int mid = (l+r)>>1;
ans += merge(pos-1,l,mid);
ans += merge(pos-1,mid+1,r);
int now = 0,x = l,y = mid + 1;
while(x<=mid||y<=r){
if(x<=mid&&y<=r) {
if(a[x]<=a[y]) temp[++now]=a[x],x++;
else temp[++now]=a[y],y++,ans+=mid-x+1;
}
else if(x<=mid) temp[++now] = a[x],x++;
else if(y<=r) temp[++now] = a[y],y++;
}
dp[pos] += ans;
for(int i=l,j=1;i<=r&&j<=now;i++,j++) a[i] = temp[j];
return ans;
}
void init(){
f[0] = 1;
for(int i=1;i<=22;i++) f[i]=f[i-1]*2;
}
int main(){
init();
int n,m;
scanf("%d%d",&n,&m);
int len = 1<<n;
for(int i=1;i<=len;i++) scanf("%lld",&a[i]);
merge(n,1,len);
while(m--){
int x;
scanf("%d",&x);
x = n-x;
for(int i=0;i<=n;i++) temp[i] = dp[i];
for(int i=0;i<=x;i++) dp[i] = f[n-i]*f[i]*(f[i]-1)/2-dp[i];
for(int i=x+1;i<=n;i++) dp[i] = dp[i] - temp[i-1] + dp[i-1];
printf("%lld
", dp[n]);
}
return 0;
}