計蒜客 - 旋轉數字

計蒜客 - 旋轉數字

蒜頭君發現了一個很好玩的事情,他對一個數作旋轉操作,把該數的最後的數字移動到最前面。比如,數 123123 可以得到 312, 231, 123312,231,123,這樣就可以得到很多個數。

現在,蒜頭君的問題是這些數中,有多少個不同的數小於原數,多少個等於原數,多少個大於原數。

旋轉中可能會出現前導零,兩數比較的時候可以忽略前導零的影響。

輸入格式

輸入一個整數 N(0<N10100000)N(0 < N\leq 10^{100000})

341

輸出格式

答案在一行中輸出三個整數,分別是小於 N,等於 N,大於 N 的個數,中間以空格隔開。

1 1 1

這道題是一道利用拓展 KMP 算法的好題目。

我們一點一點來剖析這道題。

第一個問題,怎麼枚舉出所有旋轉得到的數字?

把數字收尾相接,然後從第 0 個位置到第 length 個位置,每次往後取 length 個長度的字符,就可以遍歷出所有可能的 length 個數字。

char num[MAX_LEN];
scanf("%s", num);
int len = strlen(num);
char* numnum = duplicate(num);
for (int i = 0; i < len; i++) {
    for (int j = i; j < i + len; j++) {
        printf("%c", numnum[j]);
    }
    printf("\n");
}
char* concat(const char* s1, const char* s2) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    char* s3 = (char*)malloc(sizeof(char) * (len1 + len2 + 1));
    strncpy(s3, s1, len1);
    strncpy(s3 + len1, s2, len2);
    s3[len1 + len2] = '\0';
    return s3;
}

char* duplicate(const char* s) {
    return concat(s, s);
}

第二個問題,怎麼比較大小?

由於數字非常大,我們不可能每次都轉成整型以後去比較大小,而利用字符串相等來比較數字之間的大小,是一種常用的做法。

首先讓我們考慮怎麼比較兩個數字相等,顯然,那就是這兩個字符串相等,即 strcmp(s1, s2) == 0,對應到 KMP 算法中,那就是 next[s[i]] = length

parseInt(s1) === parseInt(s2)
// 等價於
s1 === s2

當然,這裏忽略了前導零,因爲 s1 = '001's2 = '01',顯然 parseInt(s1) === parseInt(s2) 是成立的,但是 s1 === s2 是不成立的。

可是,由於題目要求的是不同的數字,所以我們不需要考慮重複數字,那麼,在這種情況下,與原數相等的數字就只有 1 個了。

所以我們只需要比較大於,或者小於原數的數字。

給定兩個字符串 s1s2,如果已知他們開始的 x 個字符都相等,如何判斷它們的大小?只需要比較第 x + 1 個位置上的字符的大小就可以了。

而前 x 個字符都相等,這一點 next[] 數組已經幫我們做好了,所以我們只需要比較 numnum[i + next[i]]numnum[1 + next[i]] 的大小。

int greater = 0;
int equal = 0;
int less = 0;
for (int i = 1; i <= len; i++) {
    if (next[i] == len * 2) {
        equal++;
    } else if (numnum[i + next[i]] < numnum[1 + next[i]]) {
        less++;
    } else {
        greater++;
    }
}
printf("%d %d %d", less, equal, greater);

下面還剩兩個問題,第一個,如何去除循環,第二個,如何解決前導零。

在解決這兩個問題之前,我們先來看一組樣例。

對於 123123 這個數,我們期望得到的數字是 123123、231231、312312 這 3 個,而不是將 123123 變成 123123123123 之後枚舉出來的 6 個。

所以,能不能通過將數字只複製最小的循環節來完成?

答案是顯然的。

求循環節的過程之前已經介紹過了,就是利用 n - next[n] 即可,那麼我們只需複製這樣一小節拼在最後就可以了。

// 求出字符串 t 的循環節長度
int getLoop(const char *t, int length) {
    int *next = (int *) malloc(sizeof(int) * (length + 1));
    next[1] = 0;
    for (int i = 2; i <= length; i++) {
        int j = next[i - 1];
        while (t[j + 1] != t[i] && j > 0) {
            j = next[j];
        }
        if (t[j + 1] == t[i]) {
            next[i] = j + 1;
        } else {
            next[i] = 0;
        }
    }
    return length - next[length];
}

char *duplicate(const char *s) {
    int len = strlen(s + 1);
    int loop = getLoop(s, len);
    char *ss = (char *) malloc(sizeof(char) * (len + loop + 2));
    strncpy(ss + 1, s + 1, len);
    strncpy(ss + 1 + len, s + 1, loop);
    ss[1 + len + loop] = '\0';
    return ss;
}

那麼與之對應的,for (int i = 1; i <= len; i++) 中的 len,就應該換成循環節的長度 getLoop(num + 1, strlen(num + 1))

這樣一來,重複數字的問題也就自然而然地被解決了。前導零也不需要特別處理了。

但是這麼做的話,123123 可以被正常處理,123123123 也可以得到正確的結果,因爲對於他們來說,循環節都是 123,只需要拼接一段循環節到最後就可以了。

然而,12312312312 求出的循環節 123 可不能被簡單地拼接到最後,因爲對於這個數字來說,12312312312 纔是需要被拼接的。

所以我們需要判斷一下,最後一個是不是真的完整的循環節。

int loop = length - next[length];
if (next[length] % loop != 0) {
    return length;
} else {
    return loop;
}

由此,可以得到完整代碼:

#include <bits/stdc++.h>

//#define DEBUG_USE_ONLY
#define MAX_LEN 100007

void print(const char *t, const int *next, int n) {
    for (int i = 1; i <= n; i++) {
        printf("%c%c", t[i], i == n ? '\n' : '\t');
    }
    for (int i = 1; i <= n; i++) {
        printf("%d%c", next[i], i == n ? '\n' : '\t');
    }
}

// 求出字符串 t 的循環節長度
int getLoop(const char *t, int length) {
    int *next = (int *) malloc(sizeof(int) * (length + 1));
    next[1] = 0;
    for (int i = 2; i <= length; i++) {
        int j = next[i - 1];
        while (t[j + 1] != t[i] && j > 0) {
            j = next[j];
        }
        if (t[j + 1] == t[i]) {
            next[i] = j + 1;
        } else {
            next[i] = 0;
        }
    }
    int loop = length - next[length];
#ifdef DEBUG_USE_ONLY
    print(t, next, length);
    printf("[%d]\n", loop);
#endif
    if (next[length] % loop != 0) {
        return length;
    } else {
        return loop;
    }
}

char *duplicate(const char *s, int len, int loop) {
    char *ss = (char *) malloc(sizeof(char) * (len + loop + 2));
    strncpy(ss + 1, s + 1, len);
    strncpy(ss + 1 + len, s + 1, loop);
    ss[1 + len + loop] = '\0';
    return ss;
}


int *getNext(const char *t, int n) {
    int *next = (int *) malloc(sizeof(int) * (n + 1));
    next[1] = n;
    int p = 1;
    while (p < n && t[p] == t[p + 1]) p++;
    next[2] = p - 1;
    int k = 2, l;
    for (int i = 3; i <= n; i++) {
        p = k + next[k] - 1;
        l = next[i - k + 1];
        if (i + l <= p) next[i] = l;
        else {
            int j = p - i + 1;
            if (j < 0) j = 0;
            while (i + j <= n && t[i + j] == t[j + 1]) j++;
            next[i] = j;
            k = i;
        }
    }
    return next;
}

int main() {
    char num[MAX_LEN];
    scanf("%s", num + 1);
    int len1 = strlen(num + 1);
    int loop = getLoop(num, len1);
    char *numnum = duplicate(num, len1, loop);
    int len2 = strlen(numnum + 1);
    int *next = getNext(numnum, len2);
#ifdef DEBUG_USE_ONLY
    print(numnum, next, len2);
#endif
    int greater = 0;
    int equal = 0;
    int less = 0;
    for (int i = 1; i <= loop; i++) {
#ifdef DEBUG_USE_ONLY
        for (int j = i; j < i + len1; j++) {
            printf("%c", numnum[j]);
        }
        printf(", next = %d, %c - %c\n", next[i], numnum[i + next[i]], numnum[1 + next[i]]);
#endif
        if (next[i] == len2) {
            equal++;
        } else if (numnum[i + next[i]] < numnum[1 + next[i]]) {
            less++;
        } else {
            greater++;
        }
    }
    printf("%d %d %d", less, equal, greater);
    return 0;
}

歡迎關注我的個人博客以閱讀更多優秀文章:凝神長老和他的朋友們(https://www.jxtxzzw.com)

也歡迎關注我的其他平臺:知乎( https://s.zzw.ink/zhihu )、知乎專欄( https://s.zzw.ink/zhuanlan )、嗶哩嗶哩( https://s.zzw.ink/blbl )、微信公衆號( 凝神長老和他的朋友們 )
凝神長老的二維碼們

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章