1. 程式人生 > >重複字串 Fibonacci進位制 發獎金

重複字串 Fibonacci進位制 發獎金

一.重複字串(powerstr):

30分做法:列舉

長度範圍只有1000時,我們可以列舉k, 取字串第1個到第k個字元作為子串T,然後去驗證剩下的字串是否都是T重複得來

時間複雜度O(n^2)

100分做法:KMP,Next陣列

假設字串為S,長度為N,子串T重複K次後得到串S,那麼T的長度一定為L = N/K(要整除),則T = S[1...L],將S拆分成K份,每份長度為L,則有

S[1...L] =S[L+1...2L] = S[2L+1...3L]= ... = S[(K-1)L+1...KL]

由於要保證K最大,勢必L要取最小,所以根據Next函式的定義,有Next[KL] = (K-1)L;

即Next[N] = N - L,所以L = N - Next[N];

但是得出的長度L還要保證能被N整除,所以如果不能整除說明L = N,即K = 1;而如果能整除,那麼K = N / (N - Next[N]);

因而我們只要對字串S做一趟KMP,對其求Next陣列,剩下的就是上述結論

時間複雜度O(n)

考試想法:資料水的!!暴力居然是滿分!!我想拿滿分,結果只有90!!暴力有時真會出奇蹟抓狂

二.Fibonacci進位制(fib):

100分做法:DP

        N很大,先嚐試幾個小資料。可以發現,每個Fibonacci表示的長度,和Fibonacci數大小有關(1,2,3,5,8,13,21……),這些值最高位上是1,後面全是0,即第一個Fibonacci表示長度為i的數是fib[i]。因此按照長度對Fibonacci表示進行分類,長度為i的數有fib[i-1]個,看做是第i組。那麼第i組的總長度len[i] = fib[i-1]*i。

        接下來,看1出現的次數。長度為i的數的表示中,第i-1位肯定是0。

        Sum[i]表示前i組的1的個數。可以得到如下式子:Sum[i]=sum[i-1]+fib[i-1]+sum[i-2]。第i組首位1的個數為fib[i-1],i-1位為0,那麼最後的i-2位的情況,恰好為1~i-2組的所有情況。

        整體演算法也就明瞭了:

1)求出N位所在的Fibonacci表示的數的長度t

2)求1~t中Fibonacci表示中1出現的個數。

3)繼續求解剩餘字元的1。

例如求解得到最後對應Fibonacci表示為x=100100

1)對於長度為1~5的Fibonacci表示中1的個數為sum[5],i<=100000中1的個數即為sum[5]+1。

2)對於100000<i<=100100,最高位都是1,共有fib[3]個,後三位1的個數為sum[2]+1。

3)1的總個數為sum[5]+1+fib[3]+sum[2]+1。

最後細節比較多,要實現的仔細一些。

 考試想法:考試裡不要慌,可以先寫暴力再找規律,要多動筆。我因動筆不夠多,程式碼多了一倍,查錯查了半天。

三. 發獎金(reword):

100分做法:組合+質因數分解+逆元+中國剩餘定理

題目相當於求n個數的和不超過m的方案數。
首先如果是恰好等於m,那麼就等價於求方程x1 + x2 + ... + xn = m的解的個數,利用插板法可得到公式:C(n + m - 1, m)
現在要求不大於m的,相當於對i = 0 ... m對C(n + i - 1, i)求和,根據pascal遞推式可以得到答案為C(n + m, m)
現在就需要求C(n + m, m) mod P

這裡我們主要要解決如何快速計算n! mod P

以及當分母有m! mod P的情況

1.     當n,m都比較小的時候,同時P為比較大的素數時,可以直接利用逆元求解,當n,m比較大的時候,見下面兩種情況(以下參考魏銘2011年國家集訓隊作業)

2.     當P為素數的情況:

我們發現n! mod P的計算過程是以P為週期的,舉例如下:

n = 10, P = 3

n! = 1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9 * 10

  = 1 * 2 *

    4 * 5 *

    7 * 8 *

    10 *

    3 * 6 *9

  = (1 * 2)3*

    33* (1 * 2 * 3)

最後一步中的1 * 2 *3可遞迴處理。

因為P的倍數與P不互質,所以P的倍數不能直接乘入答案,應當用一個計數器變數cnt來儲存答案中因子P的個數。

我們提前預處理出fac[i]= 1 * 2 * 3 * … * (i – 1) * i mod P,函式calcfac(n)返回n! mod P的值,power(a, b, c)返回ab mod c的值,可用快速冪在O(logb)的時間內完成。

typedef long long LL;

LL calcfac(LL n)

{

      if (n<P) return fac[n];

      LL seg= n / P, rem = n % P;

      LL ret= power(fac[P - 1], seg, P); //fac[P - 1]重複出現了seg次

      ret =ret * fac[rem] % P; //除去seg次fac[P – 1]外,剩下的零頭

      cnt +=n / P; //提出n / P個因子P

      ret =ret * calcfac(n / P) % P; //遞迴處理

      returnret;

}

於是n! mod p的計算可在O(logn)的時間內解決。

對於分母中的n!,方法是相似的。若a為正整數,a * a’ = 1(mod P),那麼我們稱a’為a的逆元,記作a-1,並有b / a(mod P) = b * a-1(mod P)。這樣我們只需要把預處理fac[i] = 1 * 2 * 3 * … *(i – 1) * i mod P更換為inv[i] = 1-1* 2-1 *  3-1* … * (i– 1) -1 * i-1 mod P,其計算方法與分子中的n!計算相差無幾,具體可參考我的程式碼。求逆元可以使用擴充套件歐幾里得演算法。

3.     當p為合數時

對於某些測試點,我們發現P分解後只有2個因子,並且不相同,所以我們可以對這兩個因子分別執行演算法2,最後用中國剩餘定理合併即可。

對於剩下的資料,可以對P進行質因數分解,得到P = p1c1* p2c2 * … * ptct

對於每個1≤i≤t,以pici為模執行演算法2,最後用中國剩餘定理合併。這裡pici不一定為質數,不過只需對原演算法稍加修改即可。令P = pici,fac[i] = 除去pi的倍數外i的階乘。例如pi =3,ci = 2,那麼fac[10] = 1* 2 * 4 * 5 * 7 * 8 * 10,除去了3的倍數3、6和9。階乘依然是以P為週期的,calcfac(n)與演算法2主體相同,只是統計因子個數時,應使用ret += n / pi而不是ret += n / P,遞迴處理時也應該是calcfac(n / pi)而不是calcfac(n / P)。

時間複雜度O(t * n)

考試想法:此題哪位大神有高見,給予支援!!微笑考試時妥妥拿個暴力。。。

標程—————————————————————————————————————————————————————


第一道題歸位
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char a[1000005];
int next[1000005],len;
void getnext(){
    int i=0,j=-1;
    next[0]=-1;
    while(i<len){
        if(j==-1 || a[i]==a[j]){
            i++,j++;
            next[i]=j;
        }else j=next[j];
    }
}
int main(){
    freopen("powerstr.in","r",stdin);
    freopen("powerstr.out","w",stdout);
    int n;
    scanf("%d",&n);
    while(n--){
        scanf("%s",a);
        //if(a[0]=='.') break;
        len=strlen(a);
        getnext();
        int pos=next[len];
        if(len%(len-pos)!=0) printf("1\n");
        else printf("%d\n", len/(len-pos));
    }
    return 0;
}