Codeforces_414C:Mashmokh_and_Reverse_Operation(想法题)

12 篇文章 0 订阅
8 篇文章 0 订阅

题意大致是说给定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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值