2020牛客多校第四场 H Harder Gcd Problem

H

   (只会做3题所以在比赛的时候就开始写题解了 )
   题意是说对于 1 , 2.... n 1, 2....n 1,2....n,找到最多的数对 ( i , j ) ( 1 ≤ i , j ≤ n ) (i, j) (1 ≤ i, j≤ n) (i,j)(1i,jn) (一个数字只能出现在一个数对里),满足 g c d ( i , j ) > 1 gcd(i, j) > 1 gcd(i,j)>1
   
   经过简单分析,我们发现 1 1 1 不行,也易得大于 n / 2 n / 2 n/2 的质数也不行,那其他的数能不能互相两两成对呢?我是这样构造的:把数字按照其最大质因数进行分组然后从后往前匹配,以 n = 27 n = 27 n=27 为例:

最大质因数数字
22, 4, 8, 16
33, 6, 9, 12, 18, 24, 27
55, 10, 15, 20, 25
77, 14
1111, 22
1313, 26
1717
1919
2323

    17 , 19 , 23 17,19,23 17,19,23 都是大于 26 / 13 = 2 26 / 13 = 2 26/13=2 的质数,肯定是匹配不到的,然后对于质数 13 13 13, 可以让 13 13 13 26 26 26 匹配,对于质数 11 11 11,可以让 11 11 11 22 22 22 匹配,对于质数 7 7 7,可以让 7 7 7 14 14 14 匹配。
   而质数 5 5 5 有5个元素该怎么办呢?我们可以发现,对于质数 p p p,它集合里的第一个元素是 p p p, 第二个元素是 2 p 2p 2p,若是两两配对还多一个的话,我们可以考虑把 2 p 2p 2p 放入质数 2 2 2 的集合里。所以我们匹配 5 5 5 15 15 15 20 20 20 25 25 25,把 10 10 10 放入质数 2 2 2 的集合里。
    同理对于质数 3 3 3 的集合,我们可以将 6 6 6 放入质数 2 2 2 的集合里, 这样质数 2 2 2 里就有 { 2 , 4 , 8 , 16 , 10 , 6 } \{2, 4, 8, 16, 10, 6\} {2,4,8,16,10,6}, 剩下的两两匹配即可。

   最大质因数我们可以提前筛出来,然后每次从后往前遍历的操作是 O ( n ) O(n) O(n)

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<double, double> P;
const int maxn = 2e5 + 10;
const int INF = 0x3f3f3f3f;
const double eps = 1e-11;
const ll mod = 1e9 + 7;

int vis[maxn], mark[maxn], tot, record[maxn][2];
vector<int> v[18000];

void Prim()
{
    for(int i = 2; i <= 2e5; i++)
    {
        if(!vis[i])
        {
            mark[i] = tot++;         //记录质数的编号,mark[2] = 0,mark[3] = 1...
            for(int j = 1; j * i <= 2e5; j++)
                vis[i*j] = i;        //记录最大质因数
        }
    }
}

int main()
{
    Prim();
    int t, cnt, n, mx;
    scanf("%d", &t);
    while(t--)
    {
        cnt = 0;
        mx = 0;
        scanf("%d", &n);
        for(int i = 2; i <= n; i++)
        {
            v[mark[vis[i]]].push_back(i);    //vis[i]记录了最大质因数,mark[vis[i]]记录了质因数的编号
            mx = max(mx, mark[vis[i]]);      //记录出现最大的质因数
        }

        for(; mx >= 1; mx--)
        {
            if(v[mx].size() == 1)
            {
                v[mx].clear();
                continue;
            }
            if(v[mx].size() % 2)       //奇数个
            {
                v[0].push_back(v[mx][1]);          //将第2个元素push入v[0],即质数2的元素集合
                record[++cnt][0] = v[mx][0], record[cnt][1] = v[mx][2];
                for(unsigned int j = 4; j < v[mx].size(); j += 2)
                    record[++cnt][0] = v[mx][j], record[cnt][1] = v[mx][j-1];
            }
            else
            {
                for(unsigned int j = 1; j < v[mx].size(); j += 2)
                    record[++cnt][0] = v[mx][j], record[cnt][1] = v[mx][j-1];
            }
            v[mx].clear();               //由于有多组输入,一定要清空
        }
        for(unsigned int j = 1; j < v[0].size(); j += 2)
            record[++cnt][0] = v[0][j], record[cnt][1] = v[0][j-1];
        v[0].clear();

        printf("%d\n", cnt);
        for(int i = 1; i <= cnt; i++)
            printf("%d %d\n", record[i][0], record[i][1]);
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值