数论 + 贪心 - Harder Gcd Problem - 2020牛客暑期多校训练营(第四场)+ Jzzhu and Apples - CF 449C
题意:
T组测试数据,
每组包括一个整数n,
要求从1到n的排列:{1,2,…,n}
中,选择出两个子集A和B。
满足:∣A∣=∣B∣=m且A∩B=?。
且对于任意的元素ai?∈A,bi?∈B,gcd(ai?,bi?)>1,1≤i≤m。
求出m的最大值,并输出长度为m的两个集合A和B。
输入描述:
首行一个正整数T,表示测试数据的数量
接下来T行,每行输入正整数n(4≤n≤2×105)。
输出描述:
首行一个正整数sum,表示m的最大值。
接着输出sum行数,第i行为ai?和bi?,1≤i≤m。
示例1
输入
2
4
10
输出
1
2 4
4
3 9
5 10
8 2
4 6
分析:
出CF原题是我没想到的(出题人是不是有点偷懒...
原题链接:《Jzzhu and Apples》
本题只是改成了T组测试数据。
分析一下贪心策略吧,下午做的时候是想得很接近了,写到一半放弃(蔡。
首先应该能够发现,有公约数的数之间可以任意匹配,特殊的是质数,
对于质数P,若P×2>n,则无需考虑这个质数。
对于满足2×P≤n的质数,可以用P的倍数与之配对,这样才能保证尽量多的数被使用了。
以因子P为划分依据,将P的倍数都存储到同一个集合中去,相同集合中任意两个数均能形成一对匹配。
输出时,我们将同一集合中的元素两两输出即可。
注意:
①、要从大到小枚举质数P。
理由:假设有P1?<P2?,P1?所在集合中SP1??,P2?所在集合SP2??。
其中SP1??={
P1?,2P1?,...,?P1?n??P1?},
SP2??={
P2?,2P2?,...,?P2?n??P2?},
则必有∣SP1??∣>∣SP2??∣,若存在SP1??∩SP2??=kP1?,此时我们让kP1?与SP2??中的元素匹配才是最优的。
②、当集合中的元素大于1且为奇数时,我们就将2×P保留下来,将剩下的数两两匹配。
因为,2是最小的质数,可操作性更强,即集合S2?中的元素数量最多。
③、最后要将所有未被匹配的偶数累加上,即因子为2(最小的质数)的未被匹配的数的集合。
疑问:这样的做法能够保证考虑过1~n中的所有数了吗?
答案是肯定的,参考埃式筛法。
因为每个质数都被考虑到了,且每个质数的倍数也都被对应考虑到了。
另外:
输出时下标可从1开始两两输出,可以直接过滤掉数组中的元素数量只有1个的不合法情况。
代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <iostream>
#include <algorithm>using namespace std;const int N=2e5+10;int primes[N],cnt;
bool st[N];
bool vis[N];void get_prime(int n)
{for(int i=2;i<=n;i++){if(!st[i]) primes[cnt++]=i;for(int j=0;primes[j]*i<=n;j++){st[primes[j]*i]=true;if(i%primes[j]==0) break;}}
}int main()
{get_prime(2e5);int T,n;cin>>T;while(T--){vector<int> res[N];scanf("%d",&n);for(int i=0;i<=n;i++) vis[i]=false;int sum=0;for(int i=cnt-1;i>0;i--){int p=primes[i];for(int j=p;j<=n;j+=p)if(!vis[j]) {res[i].push_back(j);vis[j]=true;}int S=res[i].size();if(S>1 && S&1) {int tmp=res[i][1];res[i].erase(res[i].begin()+1);vis[2*p]=false;S--;}sum+=S/2;}for(int i=2;i<=n;i+=2)if(!vis[i])res[0].push_back(i);sum+=res[0].size()/2;printf("%d\n",sum);for(int i=0;i<cnt;i++){int t=res[i].size();for(int j=1;j<t;j+=2) printf("%d %d\n",res[i][j-1],res[i][j]);}}return 0;
}