题目链接:https://ac.nowcoder.com/acm/contest/7225/A
题目描述
牛牛拿到了一个长度为N的排列和M个区间,一开始排列是1、2、3......N。
然后他将这些区间在按顺序在排列上翻转,全部翻转一遍称一次操作。
现在他要去搞文化了...所以拜托你告诉他经过K次操作后的排列长什么样子。
输入描述:
第一行三个整数:N,M,K。
接下来M行,每行两个整数L和R描述一个区间。
输出描述:
输出一行N个数,表示经过K次操作后的排列。
基本思路
一开始,我认为对于任何的操作,只要操作两遍就等于没有操作,也就是说所有偶数次的操作都可以被忽略,然而……
事实证明,我这个方法是不对的,这题也不可能这么简单(实际上我到现在没有找到反例)
那么换种思路
记录变化量
一开始我想到的是,可以先翻转一次,记录这个数组的变化,也就是哪些位置发生了交换。
之后,就可以直接使用这个变化量来循环操作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;
}