题意大致是说给定2^n个数字,与m次操作,每次操作包含一个数字q,表示先将原数列每隔2^q个数字分成一组,共分为2^(n-q)组,之后再将每组内的数字取逆序,比如1 2 3取逆序后就变成3 2 1,要求每组操作之后输出整个包含2^n个数字的序列的逆序对个数.
解法是先归并排序预处理f[n][]求逆序对和顺序对,其中F[n][0]表示在归并过程中归并两组2^n长度的序列时得到的逆序对个数,f[n][1]为对应的顺序对个数,相等情况特殊处理.
由于在每隔2^q个数就将原序列逆置一次,如果设想还原之前归并排序的过程,那么很容易就能发现不论当前操作数为q的操作进行与否,当完成长度为2^q及更短的序列的归并过程再归并两个长度2^q序列时,对应的两个长为2^q的序列的状态时唯一确定的,即是已经排好序的状态,所以在这层及更上层的归并求得的逆序对/顺序对的个数是不变的,也就是这轮操作对于两组2^Q(Q>=q)长的序列归并的过程是没有影响.而在归并两个长为2^Q(Q<q)的序列的时候,因为当前操作q,所有长为2^q都逆置了,所以很容易就能发现,在求2^Q(Q<q)的情况时,所有逆序对都变为了顺序对,原本的顺序对也变为了逆序对,而这两个值也均以预处理求出.到这里,问题便已解决,细节详见程序.
#include<iostream>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<math.h>
using namespace std;
#define ll long long
const double eps=0.00000001;
ll n,m,a[(1<<20)+1],f[21][2],b[(1<<20)+1],ans,c[21];
int lg2(int x){return (int)(log(x*1.0)/log(2.0)+eps);}
void merge(int left,int right)
{
if(left==right)return;
int mid,i,j,k,now;
mid=(left+right)>>1;now=lg2(right-mid);
merge(left,mid);merge(mid+1,right);
i=left;j=mid+1;
while(i<=mid&&j<=right){if(a[i]<a[j]){f[now][1]+=right-j+1;i++;}else j++;}
i=left;j=mid+1;k=left;
while(i<=mid&&j<=right){if(a[i]>a[j]){f[now][0]+=mid-i+1;b[k++]=a[j++];}else b[k++]=a[i++];}
while(i<=mid)b[k++]=a[i++];
while(j<=right)b[k++]=a[j++];
for(i=left;i<=right;i++)a[i]=b[i];
return;
}
int main(void)
{
int i,k;
scanf("%I64d",&n);
for(i=1;i<=(1<<n);i++)scanf("%I64d",a+i);
merge(1,(1<<n));
ans=0;for(i=0;i<=n;i++)ans+=f[i][0];
scanf("%d",&m);
while(m--)
{
scanf("%d",&k);
while(k--){ans=ans-f[k][c[k]]+f[k][1-c[k]];c[k]=1-c[k];}
printf("%I64d\n",ans);
}
return 0;
}