牛客多校第四場 H-Harder Gcd Problem【素數篩+貪心】

題意

  • 連接Harder Gcd Problem
  • 給出一個數列{ 1 ,2 ,…… ,n } ,每次從中取兩個不互質的數進行匹配,取過的不能再取,問最多能取多少次,並輸出匹配方案(不惟一)

解題思路

  • 枚舉全部>=2且<=n/2的質因子 p,考慮p 的全部>=3且kp<=n的倍數、且未被匹配的數,任意將它們進行匹配。
  • 若是個數是奇數就與 p不然將p與2p匹配。
  • 最後把剩下的偶數都隨意匹配一下。n以具體的數字爲例,方便找到規律,理順思路。

舉個具體的例子,n=26,最小素因子是2,爲了不重複,咱們只需枚舉小於等於n/2的素數:2,3,5,7,13。web

求這些素數的倍數(小於等於n),列出表格方便觀察,以下:svg

p p*2 p*3 p*4 p*5 p*6 p*7 p*8 p*9 p*10 p*11 p*12 p*13
2 4 6 8 10 12 14 16 18 20 22 24 26
3 6 9 12 15 18 21 24
5 10 15 20 25
7 14 21
13 26

不難發現,同一行或者同一列(列>1)的任意兩個數字都不互質,能夠任意配對。可是在表格中數字會有重複,因爲每一個數字只能用一次,因此咱們要想一個貪心策略來選數。觀察上表,能夠發現第2列的全部數字都會在第1行再次出現,第2列到第13列中的數字也可能會屢次出現,而只有第1列的全部數字只會出現一次。spa

考慮每一行之間的數字互相配對的方案。
(1)因爲第1行(都是偶數)比較特殊,不妨從第2行開始配對,也就是把偶數留下來最後配對。
(2)因爲第1列(都是素數,只出現一次)和第2列(都是偶數,而且在第1行再次出現)比較特殊,因此每行的配對都先從第3列開始。
①若是第3列後沒被用過的數字個數爲奇數個,則將最後一個可行數字與第1列的數字配對。
緣由:這樣就會留下第2列的數字沒配對,不要緊,它們都是偶數,那麼就必定在第1行出現,只要最後在第1行配對全部沒被用過的偶數便可。.net

②若是第3列後沒被用過的數字個數爲偶數個,則將第2列的數字與第1列的數字配對。
緣由:這裏就體現出了貪心策略,由於第2列的數字都在第1行再次出現,也就是說它們能夠和第1行的任意數字配對,可是若是那樣配對就會消耗第1行的數字,總配對數顯然會少於第2列與第1列數字進行配對的方案。應該要留更多的機會讓第1行數字互相配對。
(3)最後配對第1行的數字,即配對全部還沒被用過的偶數。code

引用文章:傳送門xml


代碼

#include<stdio.h>
#include<vector>
#include<string.h>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=2e5+5;
int prime[N],vis[N];
vector<int>ans;
void init()
{
	memset(vis,0,sizeof(vis));
	vis[1]=1;int cnt=0;
	for(int i=2;i<=N;i++)
	{
		if(!vis[i])//i是質數 
			prime[++cnt]=i;
		for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
		{
			vis[i*prime[j]]=1;
            if(i%prime[j]==0)break;
		}
	}
}
int main()
{
	int t,n,c;
	init();
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		ans.clear();
		int maxn=n/2;
		memset(vis,0,sizeof(vis));
		for(int i=2;prime[i]<=maxn;i++)//從第二個素數(第二行)開始枚舉
		{
			int p=prime[i],cnt=0;
			for(int j=3;j*p<=n;j++)//從第三列開始枚舉 
			{
				int x=j*p;
				if(!vis[x])
				{
					ans.push_back(x);
					vis[x]=1;
					cnt++;
				}
			} 
			if(cnt%2==1)//若該行除前兩列外未被選中的數爲奇數個,則與第一列的數匹配 
			{
				ans.push_back(p);
				vis[p]=1; 
			}
			else{//若該行除前兩列外未被選中的數爲偶數個,則第一列與第二列的數匹配 
				ans.push_back(p);
				ans.push_back(p*2);
				vis[p]=1; 
				vis[2*p]=1; 
			}
		} 
		int cnt=0; 
		for(int i=1;i<=maxn;i++)//最後處理第一行剩下的偶數對
		{
			int x=2*i;
			if(!vis[x])
			{
				ans.push_back(x);
				vis[x]=1; 
				cnt++;
			}			
		}
		if(cnt%2==1)ans.pop_back();//若剩餘的偶數爲奇數個則有一個是不能找到匹配的將最後一個數彈出
		int m=ans.size();
		printf("%d\n",m/2); 
		for(int i=0;i<m;i+=2)
			printf("%d %d\n",ans[i],ans[i+1]);
	}
	return 0;
}