- 给定一个长度为\(n\)的排列,可以进行两种操作:把最后一个数移到开头;把第三个数移到开头。
- 一段连续的相同操作可以合并为一块操作,求一个操作序列把排列变为\(1,2,...,n\)。
- \(n\le2000\),操作序列长度需\(\le n^2\)
构造思路
我们枚举\(i=2\sim n\),每次尝试让\(i\)紧贴到\(i-1\)后面。
能改变相对顺序的只有第二种操作,且每次可以将一个数向前移动两位。
因此我们就不断将\(i\)前移,直至它的前两个数中有\(i-1\)。
如果\(i-1\)就是它前面这个数就完事了,否则我们需要把夹在它们两个之间的数\(t\)给移出去。
我们依然可以将\(t\)向前移两位,但注意\(i-1\)前面根据先前操作必然是连续的\(1\sim i-1\),我们不能让\(t\)混在其中打乱已有的顺序。
所以我们至少要将\(t\)前移\(\lceil\frac{i-1}2\rceil\)次。
然后这时候就要注意判断无解的情况了,如果\(i=n-1\),则\(t\)只可能是\(n\),那么\(t\)必须要恰好被移到\(i\)的后面。又因为我们只能两位两位一移,所以此时\(n\)为奇数则无解。
具体实现中要维护好这个排列,只需维护每个位置上的数\(a_i\)以及每个数所在的位置\(p_i\)即可。
代码:\(O(n^2)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define pre(x) ((x)^1?(x)-1:n)//上一位坐标
#define dis(x,y) ((x)-(y)+n)%n//两位间距离
#define Print(op,x) (x)&&(tot+=sprintf(s+tot,op,x),++cnt)//存储操作,同时记录操作数
using namespace std;
int n,a[N+5],p[N+5],cnt,tot;char s[N*N*10];
int x=1;I void Move(CI p3)//将位置p3上的数前移两位
{
RI v3=a[p3],p2=pre(p3),v2=a[p2],p1=pre(p2),v1=a[p1];//记录当前位及前两位
Print("%da ",dis(x,p1)),x=p1,Print("%db ",1),p[a[p1]=v3]=p1,p[a[p2]=v1]=p2,p[a[p3]=v2]=p3;//光标移到p1,执行b操作,更新这三位信息
}
int main()
{
RI i,j;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i),p[a[i]]=i;
RI t;for(i=2;i<=n;++i)//让i紧贴到i-1后面
{
W(a[pre(p[i])]^(i-1)&&a[pre(pre(p[i]))]^(i-1)) Move(p[i]);//如果前两位中没有i-1
if(a[pre(p[i])]==i-1) continue;if(i==n-1&&n&1) return puts("NIE"),0;//如果恰好紧贴完事,如果i=n-1且n为奇数无解
for(t=a[pre(p[i])],j=1;j<=(i>>1);++j) Move(p[t]);//将中间数前移ceil((i-1)/2)次
}return Print("%da ",dis(x,p[1])),printf("%d\n",cnt),puts(s),0;//最后把光标移到1所在位置
}