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

本文介绍了一种解决寻找最多数对(i,j),满足1≤i,j≤n且gcd(i,j)>1的问题的算法。通过按最大质因数分组并从后向前匹配的方法,实现了O(n)的时间复杂度。

H

   (只会做3题所以在比赛的时候就开始写题解了 )
   题意是说对于 1,2....n1, 2....n1,2....n,找到最多的数对 (i,j)(1≤i,j≤n)(i, j) (1 ≤ i, j≤ n)(i,j)(1i,jn) (一个数字只能出现在一个数对里),满足 gcd(i,j)>1gcd(i, j) > 1gcd(i,j)>1
   
   经过简单分析,我们发现 111 不行,也易得大于 n/2n / 2n/2 的质数也不行,那其他的数能不能互相两两成对呢?我是这样构造的:把数字按照其最大质因数进行分组然后从后往前匹配,以 n=27n = 27n=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,2317,19,2317,19,23 都是大于 26/13=226 / 13 = 226/13=2 的质数,肯定是匹配不到的,然后对于质数 131313, 可以让 131313262626 匹配,对于质数 111111,可以让 111111222222 匹配,对于质数 777,可以让 777141414 匹配。
   而质数 555 有5个元素该怎么办呢?我们可以发现,对于质数 ppp,它集合里的第一个元素是 ppp, 第二个元素是 2p2p2p,若是两两配对还多一个的话,我们可以考虑把 2p2p2p 放入质数 222 的集合里。所以我们匹配 555151515202020252525,把 101010 放入质数 222 的集合里。
    同理对于质数 333 的集合,我们可以将 666 放入质数 222 的集合里, 这样质数 222 里就有 {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]);
    }
}

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值