思路:
- 题目意思就是需要将1 ~ n 的数分成尽量大的俩序列a,b;使得gcd(a[i],b[i]) > 1.
- 思路还是挺简单的,筛出 1 ~ n/2 的素数,并先匹配这些素数。(2虽然是素数,但不算入这些素数行列,因为2是所有偶数的因子对后面的匹配会产生影响)
- 对于素数x,找到 x * 1,x * 3, x * 5……直到n的所有数;若这些为奇数个,那么让 x * 1与 x * 2匹配,剩下的两两匹配;若这些数为偶数个,就直接两两匹配即可。
- 这样将1 ~ n/2的所有素数处理一遍,对于1 ~ n 的所有奇数基本就都已经被匹配了,剩下的就都是偶数了。
- 对于剩下的偶数就随便两两匹配即可。
代码实现:
#include<stdio.h>
#include<ctype.h>
const int N = 2e5 + 5;
int t, n;
int pr[N], a[N], b[N], c[N], d[N], vis[N];
int primes[N], res;
bool st[N];
void get_primes(int n) //埃氏筛素数
{
for(int i = 2; i <= n; i ++){
if(!st[i]){
primes[res ++ ] = i;
pr[i] = 1;
}
for(int j = 0; primes[j] <= n / i; j ++){
st[primes[j] * i] = 1;
if(i % primes[j] == 0) break;
}
}
}
inline void read(int &x){
char t=getchar();
while(!isdigit(t)) t=getchar();
for(x=t^48,t=getchar();isdigit(t);t=getchar()) x=x*10+(t^48);
}
int main()
{
read(t);
get_primes(N);
while(t --){
read(n);
int tt1 = 0, tt2 = 0; //初始化标记数组
for(int i = 2; i <= n; i ++) vis[i] = 0;
for(int i = 2; i <= n; i ++){
//n/2以后的素数是找不到匹配的
if(pr[i] && i > n / 2) continue;
if(pr[i] && i <= n / 2 && i > 2){
int tt3 = 0, tmp = n / i;
//先匹配素数i和奇数(此奇数正好是素数的倍数)
for(int j = 1; j <= tmp; j += 2)
if(!vis[i * j]) c[++ tt3] = i * j, vis[i * j] = 1;
//因为是从前往后寻找,所有一般i * 2不会被占用
if(tt3 % 2 && !vis[i * 2]){
a[++ tt1] = i;
b[tt1] = i * 2;
vis[i * 2] = 1;
for(int j = 2; j < tt3; j += 2)
a[++ tt1] = c[j], b[tt1] = c[j + 1];
}
else{
for(int j = 1; j < tt3; j += 2)
a[++ tt1] = c[j], b[tt1] = c[j + 1];
}
}
//如果是偶数且没有被匹配过
if(!vis[i] && i % 2 == 0) d[++ tt2] = i;
}
//a数组的大小加上d数组的1/2就是匹配成功的对数
printf("%d\n", tt1 + tt2 / 2);
for(int i = 1; i <= tt1; i ++)
printf("%d %d\n", a[i], b[i]);
for(int i = 1; i < tt2; i += 2)
printf("%d %d\n", d[i], d[i + 1]);
}
return 0;
}