Codeforces 432C (哥德巴赫猜想的巧妙应用)


C.Prime Swps
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

You have an array a[1], a[2], ..., a[n], containing distinct integers from 1 to n. Your task is to sort this array in increasing order with the following operation (you may need to apply it multiple times):

  • choose two indexes, i and j (1 ≤ i < j ≤ n(j - i + 1) is a prime number);
  • swap the elements on positions i and j; in other words, you are allowed to apply the following sequence of assignments: tmp = a[i], a[i] = a[j], a[j] = tmp (tmp is a temporary variable).

You do not need to minimize the number of used operations. However, you need to make sure that there are at most 5n operations.

Input

The first line contains integer n (1 ≤ n ≤ 105). The next line contains n distinct integers a[1], a[2], ..., a[n] (1 ≤ a[i] ≤ n).

Output

In the first line, print integer k (0 ≤ k ≤ 5n) — the number of used operations. Next, print the operations. Each operation must be printed as "i j" (1 ≤ i < j ≤ n(j - i + 1) is a prime).

If there are multiple answers, you can print any of them.

Sample input

3

3 2 1

Sample output

1

1 3


Sample input

2

1 2

Sample output

0


Sample input

4

4 2 3 1

Sample output

3

2 4

1 2

2 4

地址:http://codeforces.com/problemset/problem/432/C


解:


本题题意大致是,设数列A,Ai=i (1=<i<=n) ,即1,2,3......n . 对于A,出题人很坏,把这个数列顺序打乱了,但没有改变数列里的数,只是进行了若干次数与数的交换,现在,要求你在限定的条件内使这个数列还原成A。
限定的条件是:每次可选取两个位置 i j ,要求 j-i+1 必须是素数,将数列里  i  j   位置上的数进行交换,最多只能进行 5*n次,n是这个数列元素个数。
不要求使得操作次数最小,只要求求出一种操作步骤,使得数列还原成A即可。



很显然,我们想到,这个数列里的数是从1到n的,并且每个元素都是唯一的,我们从1开始,一直到n,每次都进行若干次操作,然后将这个数还原到它本应该在的位置即可。即,把每个数 i 还原到 i 这个位置


又很显然,如果 i 这个位置已经是 i 了,那么就没有还原的必要了。同时,由于我们是从1 到 n 来还原的,所以每次这个数 i 要不然是在 i 这个位置上,要不然就在 i 后面的位置上。不可能在 i 前面的位置,因为我已经保证 i 前面的数 已经 还原到它应有的位置了。

对于这个数 i 来说,如果它不在 i 这个位置上,那么,它一定在 i 后面的位置上。那么,我们能不能找到几个位置,作为中间跳板,将 i 先还原到 这几个跳板 再还原到应有的位置上呢?其实是可以的,但是怎么做呢?这些位置怎么选取呢?

根据题目要求,如果我们每次选取的跳板的个数不超过4的话,那么每个数 i 还原到 i 这个位置所需要的步骤就不超过5,总步骤就不超过 5*n

先给一种不正确的想法,如果我们发现这个数 i 不在 i 这个位置上,并且不能直接换,就将 i 和前面那个数交换,再循环进行交换。这样子对于一个很大很乱的数列来说,操作步骤是有可能超过5*n的。



所以,这里可以用到哥德巴赫猜想!!

哥德巴赫猜想 现代最有名的表述是以下两点:
1.对于任何一个大于5的整数,都可以表示成3个素数的和。
2.对于任何一个大于2的偶数,都可以表示成2个素数的和。


设p1,p3,p4,p2,d1,d2,d3,x

p1表示x这个数需要到的位置,p2代表x这个数现在在的位置,p3,p4是两个跳板所在的位置
d1是p1到p3的距离+1,d2是p3到p4的距离+1,d3是p4到p2的距离+1
保证1<=p1<p3<p4<p2<=n,那么

d1=p3-p1+1
d2=p4-p3+1
d3=p2-p4+1
sum=d1+d2+d3=p2-p1+3

现在对于一个整数sum,我们就希望找到3个素数d1,d2,d3,使得sum=d1+d2+d3即可
注意一点,如果p2-p1=1或者p2-p1=2,那么,p2-p1+1=2, 是素数,p3-p2+1=3是素数,是可以直接进行交换的
所以p2-p1>=3,  sum>=6,恰好满足哥德巴赫猜想第一个表述中大于5的条件
同时,sum我们可以通过已知的p1,p2来求出来,对于这个sum,如果是偶数,那么可以拆成sum=2+d,如果是奇数,即sum=3+d

这个d一定是一个偶数,并且d>2,这就满足了哥德巴赫猜想第二个表述中大于2的条件了
那么我们只需要求出1-100000中所有偶数可以被表示出的两个数d1,d2即可,同时d3=2或3
这样子,通过

d1=p3-p1+1
d2=p4-p3+1
d3=p2-p4+1

我们就可以求出p3,p4,就可以知道交换操作了
这里的交换次数要不然等于0,要不然等于3,满足总操作步骤<=5*n的题意



最后提一下,有时候我们不妨可以站在出题人的角度来想问题。
这样子就可以猜出标称中判断结果是否正确的函数,有助于思考。

很显然,这个题目要求输出一种操作步骤,出题人一定有一个函数,这个函数就是用来模拟你的程序输出的操作步骤的。即根据你的操作步骤,将输入中给定的数列还原,最后判断还原后的数列是否满足 数列A,Ai=i (1=<i<=n)  这个条件即可。


 代码如下:


申明:代码含有个人风格,如果有产生歧义,请谅解,另外,请尊重个人版权。

//Hello. I'm Peter.
#include<cstdio>
#include<iostream>
#include<sstream>
#include<iomanip>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<functional>
#include<cctype>
#include<ctime>
#include<stack>
#include<queue>
#include<deque>
#include<vector>
#include<set>
#include<map>
#include<limits>
using namespace std;
#define iloveu cout<<"i love u"<<endl
#define input freopen("data.txt","r",stdin)
#define output freopen("output.txt","w",stdout)
#define INT (0x3f3f3f3f)*2
#define LL (0x3f3f3f3f3f3f3f3f)*2
#define len(a) (int)strlen(a)
#define clr(a) memset(a,0,sizeof(a))
#define clr_minus1(a) memset(a,-1,sizeof(a))
#define clr_INT(a) memset(a,INT,sizeof(a))
#define clr_true(a) memset(a,true,sizeof(a))
#define clr_false(a) memset(a,false,sizeof(a))
#define clr_queue(q) while(!q.empty()) q.pop()
#define clr_stack(s) while(!s.empty()) s.pop()
#define rep(i, a, b) for (int i = a; i < b; i++)
#define dep(i, a, b) for (int i = a; i > b; i--)
#define repin(i, a, b) for (int i = a; i <= b; i++)
#define depin(i, a, b) for (int i = a; i >= b; i--)
#define ll long long
#define eps 1e-9
#define MOD
#define MAXN 100100
#define N
#define M 100
bool isprime[MAXN+M];//判断一个数是否是素数
int prime[MAXN+M];//第i个是哪个素数
int num_prime;
void get_prime()//筛法求素数
{
    clr_true(isprime);
    isprime[0]=isprime[1]=false;
    num_prime=0;
    repin(i,2,MAXN)
    {
        if(isprime[i])
        {
            num_prime++;
            prime[num_prime]=i;
            repin(j,2,MAXN)
            {
                if(i*j>MAXN) break;
                isprime[i*j]=false;
            }
        }
    }
}
struct TwoPirmes//每个大于2的偶数都可以表示成两个素数之和
{
    int a,b;
}twoprime[MAXN+M];
void get_twoprimes()//求出每个大于2的偶数表示出成的素数之和
{
    int i,t1,t2,t,tail;
    for(i=4;i<=MAXN;i+=2)
    {
        t=i/2;
        tail=(int)(lower_bound(prime+1,prime+1+num_prime,t)-prime);
        while(1)
        {
            t1=prime[tail];
            t2=i-t1;
            if(isprime[t1] && isprime[t2])
            {
                twoprime[i].a=t1;
                twoprime[i].b=t2;
                break;
            }
            tail--;
        }
    }
}
int a[MAXN+M],pos[MAXN+M];//a表示数列,pos表示i这个数现在在哪里
struct Answer//存储结果
{
    int i,j;
}ans[5*MAXN];
int num_ans;
void swapswap(int p1,int p2)//将数列中位于p1和p2的两个数的因素进行交换,顺便记下来
{
    int t1=a[p1];
    int t2=a[p2];
    swap(a[p1],a[p2]);
    swap(pos[t1],pos[t2]);
    num_ans++;
    ans[num_ans].i=p1;
    ans[num_ans].j=p2;
    if(ans[num_ans].i>ans[num_ans].j) swap(ans[num_ans].i,ans[num_ans].j);
}
void resume(int x)//将给定的数列还原
{
    int p1=x;
    int p2=pos[x];
    if(p1==p2) return;
    //p1<p2
    int d=p2-p1+1;
    if(isprime[d]) swapswap(p1,p2);
    else
    {
        //不能直接交换,就用哥德巴赫猜想求出跳板
        int sum=p2-p1+3;
        int d1,d2,d3,p3,p4,t;
        if(sum%2) d3=3,t=sum-3;
        else d3=2,t=sum-2;
        p4=p2+1-d3;
        d1=twoprime[t].a;
        d2=twoprime[t].b;
        p3=d1+p1-1;
        swapswap(p2,p4);
        swapswap(p4,p3);
        swapswap(p3,p1);
    }
}
int main()
{
    get_prime();
    get_twoprimes();
    int n,t;
    cin>>n;
    repin(i,1,n)
    {
        scanf("%d",&t);
        a[i]=t;
        pos[t]=i;
    }
    num_ans=0;
    repin(i,1,n)
    {
        resume(i);
    }
    cout<<num_ans<<endl;
    repin(i,1,num_ans)
    {
        if(ans[i].i>ans[i].j) swap(ans[i].i,ans[i].j);
        printf("%d %d\n",ans[i].i,ans[i].j);
    }
}

谢谢阅读

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值