正题
题目链接: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;
}