2020 Petrozavodsk Winter Camp, Jagiellonian U Contest-A-Bags of Candies

标题是不是看不懂?没关系,简单来说就是毛子大学冬令营的题。

 

 题面看不懂?没关系,我来讲述题意:

给定一个数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\n",m+tmp+1);
    }
    
}

 

posted @ 2020-06-27 21:35  神之右大臣  阅读(498)  评论(1编辑  收藏  举报