「题解」NowCoder7225A 排列

题目链接:https://ac.nowcoder.com/acm/contest/7225/A

题目描述

牛牛拿到了一个长度为N的排列和M个区间,一开始排列是1、2、3......N。

然后他将这些区间在按顺序在排列上翻转,全部翻转一遍称一次操作。

现在他要去搞文化了...所以拜托你告诉他经过K次操作后的排列长什么样子。

输入描述:

第一行三个整数:N,M,K。

接下来M行,每行两个整数L和R描述一个区间。

输出描述:

输出一行N个数,表示经过K次操作后的排列。

基本思路

一开始,我认为对于任何的操作,只要操作两遍就等于没有操作,也就是说所有偶数次的操作都可以被忽略,然而……

Screenshot_20200905_220229.jpg

事实证明,我这个方法是不对的,这题也不可能这么简单(实际上我到现在没有找到反例)

那么换种思路

记录变化量

一开始我想到的是,可以先翻转一次,记录这个数组的变化,也就是哪些位置发生了交换。

之后,就可以直接使用这个变化量来循环操作K次了,时间复杂度为O(NK)

但是按照这个数据规模,这个时间复杂度一定没有办法AC,怎么办呢?

倍增思想

众所周知,倍增是一个特别快的算法,时间复杂度可以降低到对数级,著名的快速幂算法和ST表使用的就是倍增思想。

稍加思考,很容易发现,求出2此操作后的变化量,可以把操作一次之后的结果在按照这个变化量变化,这样就可以得到操作2次的变化量。

然后,我们就可以利用操作2次之后的结果 来得出操作两次后 数组的变化量,进而得出操作4次之后的结果。

如此往复,可以通过递推的方式得到2^n次操作之后的变化量。

又因为任何一个数都可以拆分成2^a[i]的和(其中a[i]为自然数)。

比如5次操作可以通过先进行1此操作,再进行4次操作实现,这和快速幂算法是非常接近的。

于是,我们可以使用记录变化量和倍增的方式达到O(N log K)的时间复杂度。

代码

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;

const int Max=100005;

int main(){
//	freopen("data.in", "r", stdin);
	int n, m, k, pre[Max], tmp, l, r;
	scanf("%d %d %d", &n, &m, &k);
/*	if(!(k&1)){
		for(int i=1; i<=n; i++) printf("%d ", i);
		return 0;
	}*/
	for(int i=1; i<=n; i++) pre[i]=i;
	for(int i=0; i<m; i++){
		scanf("%d %d", &l, &r);
		for(int j=l; j<=((l+r)>>1); j++){
			tmp=pre[j];
			pre[j]=pre[r-(j-l)];
			pre[r-(j-l)]=tmp;
		}
	}
	int now[Max], ans[Max], ansbak[Max];
	for(int i=1; i<=n; i++) now[i]=pre[i];
	for(int i=1; i<=n; i++) ans[i]=i;
	while(k){
		if(k&1){
			for(int i=1; i<=n; i++){
				ansbak[i]=ans[i];
			}
			for(int i=1; i<=n; i++){
				ans[i]=ansbak[now[i]];
			}
		}
		for(int i=1; i<=n; i++){
			now[i]=pre[pre[i]];
		}
		for(int i=1; i<=n; i++) pre[i]=now[i];
		k>>=1;
	}
	for(int i=1; i<=n; i++) printf("%d ", ans[i]);

	return 0;
}

posted @ 2020-09-05 22:21  TFLS-DJL  阅读(140)  评论(0编辑  收藏  举报