牛牛与交换顺序
题意:
牛牛有一个数组,数组元素是1到n(1<=n<=1e5)的排列,即数组的值在1~n范围内,且每个数字仅出现1次。
牛牛想要将该数组变为升序排列的,他可以进行如下的操作。
首先他要确定一个长度k,k的范围在1~n之间。
接下来他将会进行若干次操作。在每轮操作中他都可以选择一段长度为k的子数组,然后进行区间的翻转操作。
他可以做任意次数的操作,但是要求他每次选择的子数组区间满足Li<=Li+1
并且区间长度等于一开始选定的k,也就是说一旦某一次操作选择了数组的某个位置进行区间翻转操作,下一次做区间翻转的位置将会比上一次更靠右。
牛牛发现,并不总是存在一个k可以使得数组排序变为有序,请你告诉牛牛是否存在一个k能够在满足规则的情况下完成排序。
输入描述:
第一行输入一个正整数n表示数组的大小。
接下来输出一行n个正整数表示一个排列,即每个数的大小范围在1到n且每个正整数仅出现一次。
输出描述:
如果存在至少一个k能够使牛牛完成排序,请先在一行中输出一个"yes",然后另起一行输出一个可以满足排序的k,要求k的范围在[1,n]之间,如果有多解,你可以输出任意一个。反之如果不存在任何一个k可以完成排序,请直接在一行输出一个"no"
题解:找到 固定的 k 后
1.暴力reverse
2.构造双端队列(双指针思想)
暴力reverse法:
#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],k=1;
map<int,int> mp;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
mp[a[i]]=i;
}
for(int i=1;i<=n;i++){
if(i!=a[i]){
k=mp[i]-i+1;
break;
}
}
for(int i=1;i<=n;i++){
if(i==a[i]) continue;
if(i!=a[i+k-1]){
puts("no");
return 0;
}
reverse(a+i,a+i+k);
}
puts("yes");
printf("%d",k);
}
#include <iostream>
#include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
const int maxn=1e5+5;
int n,a[maxn],k=1;
map<int,int> mp;
struct duilie{
int zuo,you,flag;
int b[2*maxn]; // k 的值确定之后,b 数组的大小也就不变了
//且 b 数组要开maxn两倍大小
void reverse(){ //flag 在 0 和 1 之间转换
flag^=1; //不同flag对应不同操作 ,代替翻转操作
}
void push(int x){
if(flag==0) b[you++]=x; //一开始 zuo==you 且 flag==0 ,所以you++
else b[--zuo]=x; //避免覆盖前面赋的值,所以--zuo
}
int front(){ //取出待判断的元素
if(flag==0) return b[zuo];
else return b[you-1];
}
void pop(){ //指针移动,模拟区间移动
if(flag==0) zuo++;
else you--;
}
}q;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
mp[a[i]]=i; //map定位,寻找 k 的值
}
for(int i=1;i<=n;i++){
if(i!=a[i]){
k=mp[i]-i+1;
break;
}
} //找到了 k
q.zuo=maxn; //双指针初始化
q.you=maxn;
for(int i=1;i<=k;i++){ // b 数组初始化
q.push(a[i]);
}
for(int i=1;i<=n;i++){
if(i!=q.front()){
q.reverse();
if(i!=q.front()){
puts("no");
return 0;
}
}
q.pop(); //要在if语句的后面,因为不管是否匹配区间都要移动
q.push(a[i+k]);
}
puts("yes");
printf("%d",k);
}