zoukankan      html  css  js  c++  java
  • [POI2011]SEJ-Strongbox

    题目大意:

    一个有密码箱,数字是0~n-1,其中有若干个密码,密码的特点:若x是密码,y是密码,(x可以等于y)则(x+y)%n也是密码。

    给一个n(<=10^14),一个k(k<=min(250000,n)),给k个数(a[k]<n),前k-1个数不是密码,第k个数是密码。

    求在0~n-1中,最多有多少个数字是密码?

    题解:

    推荐(但是结论二的证明不太完整)

    看起来和数论有一些关系。
    而且一定是一个性质题。

    结论1:若x是密码,则gcd(n,x)是密码

    发现,x是密码,则k*x%n都是密码。

    所以,一定存在一个t,c,使得t*x-n*c=gcd(n,x)

    并且根据裴属定理,不能用x凑出一个更小的密码比gcd(n,x)更小,

    结论2:若x,y是密码,则gcd(x,y)是密码。

    根据裴属定理,p*x+q*y=gcd(x,y)有整数解。

    如果q是负数q=-q,那么就是p*x+(c*n-q)*y=gcd(x,y)+c*n*y

    那么,就存在非负数p,q使得p*x+q*y=gcd(x,y) mod n

    结论3:若x是所有密码中最小的那一个,那么,所有的密码就是x,2x,3x,...kx,并且x是n的约数。

    反证。设x是最小的,y是另一个密码,若x不是y的约数,那么gcd(x,y)<x,根据结论二,那么gcd(x,y)就是一个更小的密码。矛盾。

    所以,任意的y都是x的倍数。

    由于对于一个密码z,根据结论1,gcd(n,z)也是密码,所以,最小的密码x是gcd(n,z)的约数,也就是n的约数。

    所以,如果我们求出了满足条件的x,那么n/x就是答案。

    我们密码数量最多,所以,x必须取最小的。

    由于给了一个a[k]是密码,而x又是n的约数,所以x就一定是gcd(a[k],n)的约数。

    并且,x不能是a[1~k-1]的约数,只要是,那么x就能凑出ai,与ai不是密码矛盾。

    只要不存在这样的ai,那么x一定可以是最小的密码(裴属定理可以证明)。

    所以,我们可以枚举gcd(a[k],n)的约数,然后排序。

    从小到大枚举x,再暴力验证是否是a[i]的约数,第一个符合的x,n/x就是答案。

    O(ksqrt(n))

    代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=250000+5;
    ll n,k;
    ll gcd(ll a,ll b){
        return b?gcd(b,a%b):a;
    }
    ll a[N],fac[N];
    int tot;
    int main(){
        scanf("%lld%lld",&n,&k);
        for(int i=1;i<=k;i++)scanf("%lld",&a[i]);
        ll g=gcd(a[k],n);
        //cout<<"gg "<<g<<endl;
        for(ll i=1;i*i<=g;i++){
            if(g%i==0){
                fac[++tot]=i;
                if(i!=g/i) fac[++tot]=g/i;
            }
        }sort(fac+1,fac+tot+1);
        for(int i=1;i<=tot;i++){
            //cout<<fac[i]<<" ";
            bool fl=true;
            for(int j=1;j<=k-1;j++){
                if(a[j]%fac[i]==0){
                    fl=false;break;
                }
            }
            if(fl){
                printf("%lld",n/fac[i]);return 0;
            }
        }
        //cout<<" over "<<endl;
        return 0;
    }

     然而这个代码BZOJ过不去

    还要优化。

    发现,每次对一个因数判断所有的k个数代价太大。

    一个a[i]影响的是哪些gcd(a[k],n)的因数呢?

    一定是gcd(a[i],a[k])的因数。

    所以,我们可以把所有的gcd(a[i],a[k])的因数找出来干掉。

    然后直接再扫一遍,找到最小的没有被干掉的因数即可。

    复杂度?O(sqrt(n)+klogn+玄学)

    代码:

    #pragma GCC optimize(2)
    #pragma GCC optimize(3)
    #include<bits/stdc++.h>
    #define ri register int 
    #define numb (ch^'0')
    using namespace std;
    typedef long long ll;
    const int N=250000+5;
    void rd(ll &x){
        x=0;
        char ch;
        while(!isdigit(ch=getchar()));
        for(x=numb;isdigit(ch=getchar());x=(x<<1)+(x<<3)+numb);
    }
    ll n,k;
    ll gcd(ll a,ll b){
        return b?gcd(b,a%b):a;
    }
    ll a[N],fac[N];
    bool kil[N];
    int tot;
    int main(){
        scanf("%lld%lld",&n,&k);
        for(ri i=1;i<=k;i++)scanf("%lld",&a[i]);
        ll g=gcd(a[k],n);
        for(ri i=1;i<k;++i) a[i]=gcd(a[i],g); 
        for(ll i=1;i*i<=g;i++){
            if(g%i==0){
                fac[++tot]=i;
                if(i!=g/i) fac[++tot]=g/i;
            }
        }sort(fac+1,fac+tot+1);
        for(ri i=1;i<k;i++){
            kil[lower_bound(fac+1,fac+tot+1,a[i])-fac]=1;
        }    
        for(ri i=1;i<=tot;++i){
            if(kil[i]){
                for(int j=1;j<i;j++) {
                    if(fac[i]%fac[j]==0) kil[j]=1;
                }
            }
        } 
        for(ri i=1;i<=tot;++i){
            if(!kil[i]) {
                printf("%lld
    ",n/fac[i]);
                return 0;
            }
        }
        return 0;
    }
  • 相关阅读:
    zoj3690 Choosing number
    zoj 3724 Delivery 离线处理+线段树
    hdu4422 The Little Girl who Picks Mushrooms
    hdu3555 Bomb 简单数位dp
    HDU2089 不要62 简单数位dp
    解析JSON的四种方式
    iOS中几种传值的方式
    iOS代码里面出现乱码的和模拟器没有添加模拟器的解决方法:
    UIAlterview的使用方法
    ios集成支付宝的功能
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9777115.html
Copyright © 2011-2022 走看看