NOIP 2017.10.20 總結+心得

這裏寫圖片描述
(以此紀念S7死在8強的衛冕冠軍)
世界中的很大
今天考試,數論的第二試
怎麼說呢,今天的預計最好得分應該是240,然而實際得分130
這當然是自己的問題,然而還剩下許多可以總結的地方,尤其是第一題,做不出來簡直是自己害死的自己
第三題也算是一個警醒了,多說無益

看題先:

1。

這裏寫圖片描述
當時考試的時候,一看這個函數,恩,歐拉函數求和直接就是n了,然後感覺可以拋開前面一坨,反正m只有10,最後快速冪乘上就好
那麼問題就變成了預處理右面一坨
1e7的數據啊~~
只有O(n)了,線篩吧?
那就需要這玩意兒是一個積性函數了
約數個數,莫比烏斯,單位函數,全是積性函數。。。
那這個肯定是積性函數了
打個表驗證一下。。
咦怎麼不是積性函數!?幸好驗證了,排除線篩,想其他做法,未果,打了個1e5的表,無法編譯。。。
最後五分鐘。。表打錯了!表打錯了!表打錯了!
就是積性函數!!!!!!!!
時間不夠了。。
下午改,稍微分析一波線篩一遍出來。。

分析新的積性函數求和的時候,首先在確認積性之後,分析單個素數的函數值,然後分析素數的a次方積性函數值,得出線篩方程。

完整代碼:

#include<stdio.h>
typedef long long dnt;

const int N=1e7+10;
const int mod=1e9+7;

int primes[N],isnot[N],ptot=0;
dnt other[N],mpk[N],mpr[N],f[N],ans=0;

dnt quickmub(dnt a,dnt b)
{
    dnt rt=1;
    while(b)
    {
        if(b&1) rt=(rt*a)%mod;
        a=(a*a)%mod,b>>=1;
    }
    return rt;
}

dnt fix(dnt a)
{
    return (a%mod+mod)%mod;
}

void init(int n)
{
    isnot[1]=1,f[1]=1;
    for(register int i=2;i<=n;i++)
    {
        if(!isnot[i])
        {
            primes[ptot++]=i;
            mpr[i]=i,mpk[i]=1,other[i]=1;
            f[i]=fix(2-i);
        }
        for(register int t=0;t<ptot;t++)
        {
            int j=primes[t]*i;
            if(j>n) break ;
            isnot[j]=1;
            other[j]=i,mpr[j]=primes[t],mpk[j]=1;
            f[j]=f[i]*fix(2-primes[t])%mod;
            if(i%primes[t]==0)
            {
                other[j]=other[i];
                mpr[j]=primes[t],mpk[j]=mpk[i]+1;
                f[j]=f[other[i]]*fix(mpk[j]+1-mpr[j]*mpk[j])%mod;
                break ;
            }
        }
    }
}

int main()
{
    freopen("facsum.in","r",stdin);
    freopen("facsum.out","w",stdout);
    int n,m;
    scanf("%d%d",&n,&m);
    init(n);
    for(int i=1;i<=n;i++)
        ans=(ans+quickmub(i,m)*f[i]%mod)%mod,ans=fix(ans);
    printf("%I64d\n",ans);
    return 0;
}

今天算是早的學到了,原來覺得反正不是正解,爲什麼要驗證?然後今天就死在沒有驗算打表的暴力上orz。
不管是正解,還是暴力,該驗證的一定得驗證,不然GG,不能偷懶


2。

這裏寫圖片描述
這道題,簡直是裸的求階
一開始沒看到a,mod互質,想了好久
然後直接就寫出來了

題目就是求最小的b使得 a^b mod m == 1
特判0然後BsGs拔山蓋世大步小步阿姆斯特朗算法求解即可
注意特判m==1的情況

完整代碼:

#include<stdio.h>
#include<cstring>
#include<math.h>
using namespace std;
typedef long long dnt;

const dnt S=76543;

struct HASH
{
    dnt head[S],num;
    struct edge
    {
        dnt a,b;
        int last;
    }ed[S*2];
    void init()
    {
        num=0;
        memset(head,0,sizeof(head));
    }
    void insert(dnt a,dnt b)
    {
        dnt key=a%S;
        for(int i=head[key];i;i=ed[i].last)
            if(ed[i].a==a) return ;
        num++;
        ed[num].a=a,ed[num].b=b;
        ed[num].last=head[key];
        head[key]=num;
    }
    dnt query(dnt a)
    {
        dnt key=a%S;
        for(int i=head[key];i;i=ed[i].last)
            if(ed[i].a==a) return ed[i].b;
        return -1;
    }
}hash;


dnt exgcd(dnt a,dnt b,dnt &x,dnt &y)
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    dnt x0,y0;
    dnt d=exgcd(b,a%b,x0,y0);
    x=y0;
    y=x0-a/b*y0;
    return d;
}

dnt inverse(dnt a,dnt m)
{
    dnt x,y;
    dnt d=exgcd(a,m,x,y);
    if(d!=1) return -1;
    return (x%m+m)%m;
}


dnt BsGs(dnt a,dnt b,dnt m)
{
    hash.init();
    dnt c=(dnt) sqrt((double) m),cur=1;
    for(int i=0;i<c;i++,cur=(cur*a)%m)
    {
        if(b==cur && i!=0) return i;
        hash.insert(cur,i);
    }
    dnt base=inverse(cur,m);
    if(base==-1) return -1;
    cur=(b*base)%m;
    for(dnt i=c;i<=m;i+=c,cur=(cur*base)%m)
    {
        dnt tmp=hash.query(cur);
        if(tmp!=-1) return tmp+i;
    }
    return -1;
}

int main()
{
    freopen("group.in","r",stdin);
    freopen("group.out","w",stdout);
    int T;
    dnt n,mod;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%I64d%I64d",&n,&mod);
        if(n==1 || mod==1) printf("1\n");
        else printf("%I64d\n",BsGs(n,1,mod));
    }
    return 0;
}
/*
1
2 5
*/

對付這種一眼題也要萬分小心纔好,今天這道題一開始想到了兩種思路,然後寫了兩份代碼互相對了好久的拍纔算放心


3。

這裏寫圖片描述
這道題是真的沒思路啊
因爲題意看起來比較簡單就想了好久,
最後實在沒辦法只能暴力Lucas水過去了。。。
得分40
姑且聽了一下正解。。
什麼?5進制拆分?數位DP?組合數?EXM?

正解其實若是知道Lucas本來的性質或者說證明方法和實現原理這道題其實不難想到
但是記得當年學Lucas的時候耳畔一直迴響着某大佬的一句
“信息學奧賽沒有證明”
Lucas一直是背板子
Lucas其實就是按照mod數進制拆分,去進制下每一位的組合數在累乘起來
那麼要想一個數最後的組合數mod 5 爲0,只需要在任意一位的組合數位0就行了
那麼問題就轉換成了L到R在5進制下有至少一位的組合數爲0的數的個數
在5進制下數位DP即可

完整代碼:

#include<stdio.h>
#include<cstring>
using namespace std;
typedef long long dnt;

const int mod=5;

int T,a[100010],num[100010];
dnt saber[100010],inv[100010],f[100010][2];

void init(int n)
{
    saber[0]=inv[0]=inv[1]=1;
    for(int i=1;i<=n;i++) saber[i]=saber[i-1]*i%mod;
    for(int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=n;i++) inv[i]=inv[i-1]*inv[i]%mod;
}

dnt Misaka(dnt a,dnt b)
{
    if(a<b) return 0;
    if(a<mod) return saber[a]*inv[b]%mod*inv[a-b]%mod;
    return Misaka(a/mod,b/mod)*Misaka(a%mod,b%mod);
} 

void sov1(int L,int R,int n)
{
    dnt ans=0;
    for(int i=L;i<=R;i++) ans+=(Misaka(n,i)==0);
    printf("%d\n",ans);
}

dnt dfs(int pos,int pre,int lim)
{
    if(pos==-1) return pre;
    if(!lim && f[pos][pre]!=-1) return f[pos][pre];
    dnt ans=0;
    int up=lim ? a[pos] : 4;
    for(int i=0;i<=up;i++)
        ans+=dfs(pos-1,(pre || Misaka(num[pos],i)==0),i==up && lim);
    if(!lim) f[pos][pre]=ans;
    return ans;
}

dnt solve(dnt n)
{
    int cnt=0;
    memset(a,0,sizeof(a));
    while(n)
    {
        a[cnt++]=n%5;
        n/=5;
    }
    return dfs(cnt-1,0,1);
}

void Divide(dnt n)
{
    int cnt=0;
    memset(num,0,sizeof(num));
    while(n)
    {
        num[cnt++]=n%5;
        n/=5;
    }
}

void sov2(dnt L,dnt R,dnt n)
{
    Divide(n);
    memset(f,-1,sizeof(f));
    dnt ans=solve(R)-solve(L-1);
    printf("%I64d\n",ans);
}

int main()
{
    freopen("ccount.in","r",stdin);
    freopen("ccount.out","w",stdout);
    init(5);
    scanf("%d",&T);

    while(T--)
    {
        dnt L,R,n,ans=0;
        scanf("%I64d%I64d%I64d",&L,&R,&n);
        if(R-L+1<=5000) sov1(L,R,n);
        else sov2(L,R,n);
    }
    return 0;
}

今天算是明白了Lucas定理的時間原理了
果然如YYR學長所說,算法原理還是很重要啊~~~


今天考試的成績就在倒好不好的環境中落幕了
明天ACM,隊友都還沒定orz,加油吧~~~

嗯,就是這樣

發佈了163 篇原創文章 · 獲贊 21 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章