1. 程式人生 > 其它 >YbtOJ-選點構形【尤拉函式】

YbtOJ-選點構形【尤拉函式】

正題

題目連結:https://www.ybtoj.com.cn/contest/351/problem/1


題目大意

一個圓上,你需要在\(3\sim n\)中選出\(k\)個作為\(a_i\),然後再圓上選擇最少的點使得對於每個\(a_i\)你都能用選出的點連成一個正\(a_i\)邊形。

\(k+2\leq n\leq 10^6\)


解題思路

首先我們固定一個\(0\)點因為肯定所有的正\(a_i\)邊形都交於一個點。

然後考慮對於一個\(a_i\),我們需要的點就是\(\frac{1}{a_i}\times k(0\leq k<a_i)\)

注意到一個\(a_i\)如果存在一個\(a_j=k\times a_i\)

那麼\(a_i\)就不會產生貢獻。反過來說\(a_j\)的貢獻會減少\(a_i\)

而我們肯定是優先選擇\(a_i\)的,也就是說\(a_j\)選擇當且僅當它的所有約數都被選擇,而此時\(a_j\)產生的貢獻恰好就是\(\varphi(a_j)\)(因為所有\(a_j\)約數產生的貢獻和為\(a_j\),我們可以視為它們的貢獻都單獨為\(\varphi(x)\)

那麼我們可以把\(3\sim n\)按照\(\varphi\)排序從小到大加就好了。

至於\(1,2\),特判一下比較小的情況就可以了,\(k\)比較大時顯然\(1,2\)會被選上去,需要多選\(2\)個。

時間複雜度:\(O(n\log n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=1e6+10;
int n,k,cnt,phi[N],pri[N/10];
bool v[N];
int main()
{
	freopen("point.in","r",stdin); 
	freopen("point.out","w",stdout);
	scanf("%d%d",&n,&k);
	if(k==1)return puts("3")&0;
	if(k==2)return puts("6")&0;
	phi[1]=1;
	for(int i=2;i<=n;i++){
		if(!v[i])phi[i]=i-1,pri[++cnt]=i;
		for(int j=1;j<=cnt&&i*pri[j]<=n;j++){
			v[i*pri[j]]=1;
			if(i%pri[j]==0){
				phi[i*pri[j]]=phi[i]*pri[j];
				break;
			}
			phi[i*pri[j]]=phi[i]*phi[pri[j]];
		}
	}
	sort(phi+1,phi+1+n);
	long long ans=0;
	for(int i=1;i<=k+2;i++)ans+=phi[i];
	printf("%lld\n",ans);
	return 0;
}