H:Harder Gcd Problem
题面:
题目大意:
给你一个
n
n
n,让你从
1
—
n
1—n
1—n中选取数,组成
A
A
A,
B
B
B两个集合。
这个集合满足:
- ∣ A ∣ = ∣ B ∣ |A| = |B| ∣A∣=∣B∣。个数相等
-
g
c
d
(
a
i
,
b
i
)
>
1
gcd(ai,bi)>1
gcd(ai,bi)>1一 一对应。
输出: m m m的最大值和 A A A数组 B B B数组一一对应的 a i ai ai和 b i bi bi。
出题人题解
本人题解:(听完出题巨巨后)
这道题是道构造、贪心、筛子题。
- 我们首先思考埃氏筛的筛法:将素数倍增,并将其标记。
而将这些某个素数和其倍增的数放到集合中,这些数满足 g c d ( a i , b i ) > 1 gcd(ai,bi)>1 gcd(ai,bi)>1 吗? 基于这个思路我们来听下面的讲解。 - 当一个素数和其倍增的数(未被使用过)的总个数是偶数时,我们就可以全部使用。但当是奇数时,我们就要进行贪心,我们将 2 ∗ 当 前 素 数 2*当前素数 2∗当前素数的数不存入当前答案中,因为 2 ∗ 当 前 素 数 2*当前素数 2∗当前素数的数必定能与2及2的倍数放到一起。
- 从小于等于n的素数进行逆序循环,每一次循环标记被使用过的数。
- 这里来讲解一下为什么要逆序(本博主在比赛的时候想的是正序,想的脑子爆炸,最后听完讲解后瞬间明白了QAQ~~)
我们这样想,我们逆序循环,所使用过的数在前面不一定会被使用。但是我们正向循环的话,从2开始的话,我们将全部使用完后,如果后面某一素数及其倍增的数(未被使用)的个数为奇数的话,我们就肯定会剩余某一个数。
ACcode:
/*
* @Author: NEFU_马家沟老三
* @LastEditTime: 2020-07-20 21:30:00
* @CSDN blog: https://blog.csdn.net/acm_durante
* @E-mail: 1055323152@qq.com
* @ProbTitle:
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i, a, n) for (int i = a; i <= n; i++)
#define per(i, a, n) for (int i = n; i >= a; i--)
#define lowbit(x) ((x) & -(x))
const double PI = acos(-1.0);
const int N = 2e5 + 5;
bool vis[N];
vector<int> prime;
void get_prime()//埃氏筛
{
vis[1] = 1;
for (int i = 2; i * i <= 2e5; i++)
{
if (vis[i])
continue;
for (int j = i + i; j <= 2e5; j += i)
vis[j] = 1;
}
rep(i, 2, 2e5)
{
if (!vis[i])
prime.push_back(i);
}
}
vector<int> ans;
void solve(int n)
{
memset(vis, 0, sizeof(vis));
ans.clear();
int len = upper_bound(prime.begin(), prime.end(), n) - prime.begin() - 1;//找到小于n的最大素数
for (int i = len; i >= 0; i--)//逆序筛答案
{
int cnt = n / prime[i];
if (cnt == 1 || cnt == 0) //只有一个或没有符合
continue;
cnt = 0;//记录当前素数及倍数的次数
for (int j = prime[i]; j <= n; j += prime[i])
{
if (vis[j])//被使用过则不记录
continue;
++cnt;
}
bool flag = (cnt % 2);//偶数为0,奇数为1
for (int j = prime[i]; j <= n; j += prime[i])
{
if ((flag && j == 2 * prime[i]) || vis[j])//如果是偶数或被标记过则不存入答案
continue;
ans.push_back(j);
vis[j] = 1;//存入答案的进行标记
}
}
}
int main()
{
int t;
get_prime();//埃氏筛
scanf("%d", &t);
while (t--)
{
int n;
scanf("%d", &n);
solve(n);
printf("%d\n", ans.size() / 2);
for (int i = 0; i < ans.size(); i += 2)//输出元素
printf("%d %d\n", ans[i], ans[i + 1]);
}
return 0;
}