【洛谷3516】[POI2011] PRZ-Shift(构造题)

点此看题面

  • 给定一个长度为(n)的排列,可以进行两种操作:把最后一个数移到开头;把第三个数移到开头。
  • 一段连续的相同操作可以合并为一块操作,求一个操作序列把排列变为(1,2,...,n)
  • (nle2000),操作序列长度需(le n^2)

构造思路

我们枚举(i=2sim n),每次尝试让(i)紧贴到(i-1)后面。

能改变相对顺序的只有第二种操作,且每次可以将一个数向前移动两位。

因此我们就不断将(i)前移,直至它的前两个数中有(i-1)

如果(i-1)就是它前面这个数就完事了,否则我们需要把夹在它们两个之间的数(t)给移出去。

我们依然可以将(t)向前移两位,但注意(i-1)前面根据先前操作必然是连续的(1sim i-1),我们不能让(t)混在其中打乱已有的顺序。

所以我们至少要将(t)前移(lceilfrac{i-1}2 ceil)次。

然后这时候就要注意判断无解的情况了,如果(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
",cnt),puts(s),0;//最后把光标移到1所在位置
}
败得义无反顾,弱得一无是处
原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu3516.html