题意
给你1到n的一个排列,让你通过把将数字两两交换,把数列恢复升序。
要求交换的两数的下标i,j满足j-i+1为素数,且交换次数不超过5n。
题解
挺有意思的一道题。
首先不考虑素数的限制,要交换多少次才能实现目标呢。
答案是n次
方法是yy一下每个元素只有一个的计数排序,
从1到n,每次将元素i复位,占在i位置上的元素呢,丢到i原来在的地方就好啦。
那么现在考虑素数的限制。5次可以实现一次交换把一个元素复位嘛。显然是可以的。
答案是哥德巴赫猜想。
预处理好范围内偶数如何拆成两个素数(反正小范围内哥猜一定是对的,直接线性筛完素数后,n^2循环各种奇素数就可以得到了。而且还一定不会重复,我觉得效率挺好。)
可以通过pp[p1+p2]=p1来储存。
剩下就很简单了,先判素数,不是的话奇数就分解成3和一个偶数。偶数再分解成两个素数。
上界只要3n就够了嘛。
剩下就是只剩输出的问题了。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string.h>
#include <string>
#include <map>
#include <set>
using namespace std;
const int n=100010;
bool a[100010];
int p[10000];
int num=0;
void Prime() {
memset(a, 0, sizeof(a));
int i, j;
for(i = 2; i < n; ++i) {
if(!(a[i])) p[num++] = i;
for(j = 0; (j<num && i*p[j]<n); ++j) {
a[i*p[j]] = 1;
if(!(i%p[j])) break;
}
}
}
int pp[100010];
void PrimePair()
{
for (int i=0;i<num;i++)
for (int j=i;j<num;j++)
{
if (p[i]+p[j]>100000||(p[i]+p[j])%2==1)
break;
pp[p[i]+p[j]]=p[i];
}
}
int para[100010],pos[100010];
int T;
int ss=0;
pair<int,int> out[500010];
void step(int s,int i,int j)
{
if (!a[s]){
out[ss++]=pair<int,int>(i,j);
swap(pos[para[i]],pos[para[j]]);
swap(para[i],para[j]);
return ;
}
else{
if (s%2==0)
{
step(2,j-1,j);
step(j-i,i,j-1);
}
else
{
step(pp[s+1],j-pp[s+1]+1,j);
step(j-i-pp[s+1]+2,i,j-pp[s+1]+1);
}
}
}
int main()
{
//freopen("input","r",stdin);
//freopen("output","w",stdout);
Prime();
memset(pp,0,sizeof(pp));
PrimePair();
pp[2]=2;
cin>>T;
ss=0;
for (int i=1;i<=T;i++)
{
cin>>para[i];
//para[i]=T+1-i;
pos[para[i]]=i;
}
for (int i=1;i<=T;i++)
{
if (para[i]!=i)
step(pos[i]-i+1,i,pos[i]);
}
cout<<ss<<endl;
for (int i=0;i<ss;i++)
cout<<out[i].first<<" "<<out[i].second<<endl;
return 0;
}