AGC010 - D: Decrementing

原題鏈接

題意簡述

給出一個n(n105) 個數的序列a ,足夠聰明的AB兩人輪流進行以下操作:
令一個大於1的數減1,然後所有數除以gcd{a}
如果一個人不能操作了,那麼他就輸了。
輸入保證所有數都是正整數並且gcd{a}=1

分析

這是一道和奇偶性有關的題目。
很容易知道拿到1,1,1,...,1 就輸了,此時手裏數的和sum 等於n

考慮sum 奇偶性的轉換關係。
這裏寫圖片描述

或者再展開一點:
這裏寫圖片描述

偶-奇必然的很好理解,重點考慮一下sum 爲奇數的情形。
奇(-偶)-奇 要求gcd爲偶數,因爲偶/奇=偶。因此原數列%2必然是000…01的形式,而我可以將其變爲000…11從而形成奇-偶 。所以奇-奇一定條件下可選的,奇-偶任何條件下可行的。

由此再考慮n 的奇偶性對答案的影響。

  1. n 爲偶數
    能保持sum 爲奇數的一方一定不會輸。既然sum 一直是奇數,那麼就一定不會得到1,1,1,...,1 的狀態,必勝。而因爲拿到奇數的一方一定可以給對手一個偶數,而對手只能無可奈何地還你一個奇數。所以初始sum 爲奇數則先手必勝,否則必敗。
    時間複雜度爲O(n)
  2. n 爲奇數
    能保持sum 爲偶數的一方一定不會輸。但是拿到偶數的一方需要保證對手不會還回來一個奇數,下面證明這一點一定可以做到。

    證明

    首先奇數方要是想還給對手一個奇數必然要使gcd不爲1,所以原數列%gcd必然是000…01的形式。再考慮這個狀態是怎麼達到的:
    這裏寫圖片描述
    對於000…11,000…02,000…1k,000…0(k+1)這四種狀態另一方都有辦法規避000…01的結果。所以拿到偶數的一方一定能保證下一輪自己還是偶數。

    因此初始sum 爲偶數的話先手必勝。
    時間複雜度爲O(n)

    但是初始sum 是奇數並不意味着必敗;因爲此時還沒有另一方的干擾,是有可能給對手一個奇數的。但是由於你可能只有極少的選擇方案,這給了對手可乘之機:對手也有可能還回來一個奇數。以此循環往復,無法給對手奇數的一方會輸掉遊戲。
    因爲每次都會給所有數除以一個大於1的gcd,所以最多往復log2(min{a}) 次,其中每次操作的複雜度是O(n) 。時間複雜度最大爲O(nlog2(min{a}))

總時間複雜度最大爲O(nlog2(min{a}))

實現

只有nsum 均爲奇數時無法通過判斷nsum 的奇偶性來得出答案。
計算出前綴gcdg1 和後綴gcdg2 ,然後計算gcd{g1[i1],a[i]1,g2[i+1]} ,如果 (sum1)/gcd 爲奇數就令所有數除以gcd,然後輪到對手。若沒有可能的gcd,GG。

代碼

//Decrementing
#include <cstdio>
typedef long long lint;
int const N=1e5+10;
int n,a[N];
int gcd(int x,int y)
{
    if(x==-1 || y==-1) return -x*y;
    if(x==0) return y;
    else return gcd(y%x,x);
}
int g1[N],g2[N];
int main()
{
    scanf("%d",&n);
    lint sum=0;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),sum+=a[i];
    if(n%2==0)
    {
        if(sum%2==1) printf("First");
        else printf("Second");
        return 0;
    }
    if(sum%2==0) {printf("First"); return 0;}
    int cur=0;
    while(true)
    {
        bool flag=false;
        sum=0;
        for(int i=1;i<=n;i++) sum+=a[i];
        g1[0]=-1; g2[n+1]=-1;
        for(int i=2;i<=n;i++) g1[i]=gcd(g1[i-1],a[i]);
        for(int i=n-1;i>=1;i--) g2[i]=gcd(g2[i+1],a[i]);
        int g;
        for(int i=1;i<=n&&!flag;i++)
        {
            if(a[i]==1) continue;
            g=gcd(gcd(g1[i-1],g2[i+1]),a[i]-1);
            if(((sum-1)/g)%2==1) flag=true;
        }
        if(flag)
            for(int i=1;i<=n;i++) a[i]/=g;
        else
        { 
            if(cur==0) printf("Second");
            else printf("First");
            return 0; 
        } 
        cur^=1;
    }
    return 0;
}

注意

挺好寫的,主要是思維難度高

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