(只会做3题所以在比赛的时候就开始写题解了 )
题意是说对于 1 , 2.... n 1, 2....n 1,2....n,找到最多的数对 ( i , j ) ( 1 ≤ i , j ≤ n ) (i, j) (1 ≤ i, j≤ n) (i,j)(1≤i,j≤n) (一个数字只能出现在一个数对里),满足 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 为例:
最大质因数 | 数字 |
---|---|
2 | 2, 4, 8, 16 |
3 | 3, 6, 9, 12, 18, 24, 27 |
5 | 5, 10, 15, 20, 25 |
7 | 7, 14 |
11 | 11, 22 |
13 | 13, 26 |
17 | 17 |
19 | 19 |
23 | 23 |
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
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]);
}
}