标题是不是看不懂?没关系,简单来说就是毛子大学冬令营的题。
题面看不懂?没关系,我来讲述题意:
给定一个数n,一开始筐子A里有1~n一共n个数。假如A中某两个数的gcd不是1,那么这两个数可以一起放到筐子B里。
求(A中的数量+B中的数量/2)最小的数值.
构造题。
显然,尽可能让B中多A中少一定会让答案更优。
仔细观察,发现大于$frac{n}{2}$的数之间任意的gcd都是1。
那么我们枚举所有数的最大质因数p,并把它加入到集合$S[p]$中。
比如说n=9。刨除1不算,我们得到集合:$S[2]=2,4,8$,$S[3]=3,6,9$,$S[1]=1,5,7$。
注意,其中$S[1]$并不是表示最大质因数p是1,只是表示本身为质数或者1。另外由于是枚举的质因数,所以x不是质数的$S[x]$中一定没有元素。
简单分析可以得出两个性质:对于x>n/2的所有集合$S[x]$都不会有任何元素。除了$S[1]$以外的集合每个集合都至少有2个数。
证明:
大于n/2的数如果不是质数,那么它的最大质因数一定小于n/2,所属的集合一定是某个$S[x]$,其中$x<=frac{n}{2}$。如果是质数,那么它所属的集合就是$S{1}$。
对于小于n/2的一个质数x,$S[x]$一定至少包含两个元素:x和2x。
这样一来,除了$S[1]$以外的集合每个集合都至少有2个数:p和2p。根据不同情况还可能有$3p,4p,......kp$。反正不会少于2个就是啦。
根据刚才的性质可以得到:对于集合p如果元素个数是偶数,那么两两配对扔到B中。否则将其中必定存在的元素2p扔到$S[0]$这个特殊的集合中代以后使用。
在上面的操作完成后,我们考虑$S[0]$中的集合。因为$S[0]$中的元素都是形如2p这种形式,所以gcd最少也是2。因此他们也可以两两配对。如果$S[0]$的元素个数是奇数特判一下就好啦。
至于剩下的$S[1]$这个集合。我们只能把他们全部留在筐子A中。
根据上面的算法,假设$[frac{n}{2}+1,n]$之间的质数个数为m,那么答案显然是1+m+(n-m-1+1)/2。
现在问题转化为了求1~n之间的质数的个数。($n<=1e11$)
线性筛什么的不可能,这个数据范围我们只能想亚线性做法,比如......min25筛。
如果会min25筛的人这道题到此就可以愉快的AC了。
不会的人有两种选择:
1.去学min25筛。
2.我们充分运用分块打表的思想,提前求出$(1,sqrt n)$,$(sqrt n +1,sqrt n +sqrt n)$,......这些区间的质数个数,打个表。
然后问题转化为了求一个区间长度小于$1e6$,但n为$1e11$的某个区间的质数个数。
这个问题便是普及组算法了。
至此,我们已经将一个省选难度的题转化为了普及组难度的题。代码写完就轻松AC啦。
#include <bits/stdc++.h> #define inc(i,a,b) for(register int i=a;i<=b;i++) using namespace std; long long prime[1000010],num,sp1[1000010]; long long n,sqrtn,w[1000010],tot,g1[1000010],ind1[1000010],ind2[1000010]; int vis[1000010]; void pre(int n){ vis[1]=1; inc(i,1,n){ if(vis[i]==0){ prime[++num]=i; sp1[num]=sp1[num-1]+1; } for(register int j=1;j<=num&&prime[j]*i<=n;j++){ vis[i*prime[j]]=1; if(i%prime[j]==0)break; } } } double dd[1000010]; long long ffind(long long n){ num=0; tot=0; sqrtn=sqrt(n); pre(sqrtn); for(register long long i=1;i<=n;){ long long j=n/(n/i); w[++tot]=n/i; g1[tot]=w[tot]-1; if(n/i<=sqrtn) ind1[n/i]=tot; else ind2[n/(n/i)]=tot; i=j+1; } inc(i,1,num) dd[i]=(double)(1.00/prime[i]); inc(i,1,num){ for(register int j=1;j<=tot;j++){ if(prime[i]*prime[i]>w[j]) break; long long ttmp=(w[j]*dd[i]+1e-9); long long k=ttmp<=sqrtn?ind1[ttmp]:ind2[n/ttmp]; g1[j]-=g1[k]-sp1[i-1]; } } return g1[1]; } int main(){ int T;cin>>T; while(T--){ cin>>n; long long m=ffind(n); long long mp=ffind(n/2); m=m-mp; long long tmp=(n-m)/2; printf("%lld ",m+tmp+1); } }