2020牛客暑期多校训练营(第四场)补题题解

H:Harder Gcd Problem

题面:

在这里插入图片描述
在这里插入图片描述

题目大意:

给你一个 n n ,让你从 1 n 1—n 中选取数,组成 A A B B 两个集合。
这个集合满足:

  1. A = B |A| = |B| 。个数相等
  2. g c d ( a i , b i ) > 1 gcd(ai,bi)>1 一 一对应。
    输出: m m 的最大值和 A A 数组 B B 数组一一对应的 a i ai b i bi

出题人题解

在这里插入图片描述

本人题解:(听完出题巨巨后)

这道题是道构造、贪心、筛子题。

  1. 我们首先思考埃氏筛的筛法:将素数倍增,并将其标记。
    而将这些某个素数和其倍增的数放到集合中,这些数满足 g c d ( a i , b i ) > 1 gcd(ai,bi)>1 吗? 基于这个思路我们来听下面的讲解。
  2. 当一个素数和其倍增的数(未被使用过)的总个数是偶数时,我们就可以全部使用。但当是奇数时,我们就要进行贪心,我们将 2 2*当前素数 的数不存入当前答案中,因为 2 2*当前素数 的数必定能与2及2的倍数放到一起。
  3. 从小于等于n的素数进行逆序循环,每一次循环标记被使用过的数。
  4. 这里来讲解一下为什么要逆序(本博主在比赛的时候想的是正序,想的脑子爆炸,最后听完讲解后瞬间明白了QAQ~~)
    我们这样想,我们逆序循环,所使用过的数在前面不一定会被使用。但是我们正向循环的话,从2开始的话,我们将全部使用完后,如果后面某一素数及其倍增的数(未被使用)的个数为奇数的话,我们就肯定会剩余某一个数。

ACcode:

/*
 * @Author: NEFU_马家沟老三
 * @LastEditTime: 2020-07-20 21:30:00
 * @CSDN blog: https://blog.csdn.net/acm_durante
 * @E-mail: [email protected]
 * @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;
}

猜你喜欢

转载自blog.csdn.net/acm_durante/article/details/107474580