Loading

7381. 【2021.11.12NOIP提高组联考】转化

Description

\(\text{xyx}\) 喜欢双射。

他惊奇的发现, 对于所有长度为 \(n\) 的排列,交换每个排列在区间 \([l,r]\) 中的最小值和最大值,得到的排列两两不同,这构成了一个双射!

他很喜欢这个操作,于是他又掏出了两个排列 \(\left\{a_{n}\right\},\left\{b_{n}\right\}\),他希望用不超过 \(m\) 次操作来把 \(\left\{a_{n}\right\}\) 变成 \(\left\{b_{n}\right\}\)

Solution

首先发现操作可逆,因此可以将题目转换成 \(\{a_n\}\rightarrow 1\dots n\rightarrow \{b_n\}\)。而这两步是一样的,所以到此为止,这题的目的就是利用题目给出的操作来给一个序列排序。

再想到一个基本操作:reverse 一个有序区间,代价是 \(\frac{len}{2}\)。具体步骤是操作 \(\{l,r\},\{l+1,r-1\}\dots,\{mid,mid+1\}\)

直接排序没有什么好的思路,可以用分治。

分治到区间 \([l,r]\),尝试跟归并类似的操作,先让 \([l,mid]\)\([mid+1,r]\) 有序,然后在合并两个区间。

假设此时是这个样子:

考虑将 \([l,mid]\)\(\le a[\frac{r-l+1}{2}]\) 的部分放在左边,\(> a[\frac{r-l+1}{2}]\) 的部分放在右边,可以跟归并类似的使用双指针。

\([l,mid]\) 内找出应该放在右边的那一部分,也在 \([mid+1,r]\) 内找出应该放在左边的那一部分。

例如下图:

将这两个部分翻转,变成这样:

然后将两个红色的部分整体翻转,就成了这样:

这时我们的目的也就达成了,接下来就是继续向下分治了。

注意一个细节,就是将 \(b_i\) 求答案的时候,最后要讲操作反着输,因为是逆操作。

Code

#include<cstdio>
#include<algorithm>
#define N 4005
#define M 300005
using namespace std;
int n,m,res,ans1,ans2,a[N],ans[M][3];
void rever(int l,int r)
{
	for (int x=l,y=r;x<y;++x,--y)
	{
		ans[++res][1]=x;ans[res][2]=y;
		swap(a[x],a[y]);
	}
}
void Sort(int l,int r,int mid)
{
	if (l>=r) return;
	int x=l-1,y=mid;
	while (x-l+1+y-mid<(r-l+1)>>1)
		if (y==r||(x<mid&&a[x+1]<a[y+1])) ++x;
		else ++y;
	rever(x+1,mid);rever(mid+1,y);
	rever(x+1,y);
	mid=l+(x-l+1)+(y-mid)-1;
	Sort(l,mid,x);Sort(mid+1,r,y);
}
void solve(int l,int r)
{
	if (l>=r) return;
	int mid=(l+r)>>1;
	solve(l,mid);solve(mid+1,r);
	Sort(l,r,mid);
}
int main()
{
	freopen("trans.in","r",stdin);
	freopen("trans.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	solve(1,n);
	for (int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	ans1=res;
	solve(1,n);
	ans2=res;
	for (int i=ans1+1,j=ans2;i<=j;++i,--j)
		swap(ans[i],ans[j]);
	printf("%d\n",ans2);
	for (int i=1;i<=ans2;++i)
		printf("%d %d\n",ans[i][1],ans[i][2]);
	return 0;
}
posted @ 2021-11-12 23:49  Thunder_S  阅读(67)  评论(0编辑  收藏  举报