1. 程式人生 > >【數位統計】之【spoj1433 KPSUM】

【數位統計】之【spoj1433 KPSUM】

【spoj1433】KPSUM

來源

高逸涵《數位計數問題解法研究》
由於自己的數位計數類的問題實在太差了,所以把例2用markdown抄寫並補充了一遍。

題意

1N(1n1015)寫在紙上,然後在相鄰的數字間交替插入“+”和“-”,求最後的結果。例如當N=12時,答案為:12+34+56+78+91+01+11+2

分析

這是一道稍微複雜一點的數位計數問題。

我們首先探查數位確定,所有數字自由的情況。
不妨設第一位從”+”開始。

若數位個數為偶數,以6位為例:

+0 -0 +0 -0 +0 -0
+0 -0 +0 -0 +0 -1
+0 -0 +0 -0 +0 -2
+9 -9 +9 -9 +9 -9

發現:
①每個數位的符號相同,奇數符號的數位和偶數符號的數位個數相等。
②每個數位中每個數出現的次數相同。
所有數位的所有數的和為0。

若數位個數為奇數,以5位為例:

+0 -0 +0 -0 +0
-0 +0 -0 +0 -1
+0 -0 +0 -0 +2
-0 +0 -0 +0 -3
+9 -9 +9 -9 +8
-9 +9 -9 +9 -9

相鄰兩行的和為-1。

之前我們這樣認定:

不妨設第一位從”+”開始。

然而第一位不一定是+,這跟總位數k有關。
注意:當前我們考慮的只是後面n位對答案的貢獻,字首對答案的貢獻並沒有計算。

(1)當k為偶數時
①當n為偶數時,後面位數的貢獻為0;
②當n為奇數時,從第n1位開始貢獻為0,所以我們只需要考慮第n位的貢獻。
n位的全是負號,貢獻為:
[(0)+(1)+...+(9)](n1)=4510n1
(2)當k為奇數時,相鄰兩行和為-1,總共有10

n個數,所以有10n行,所以貢獻為:
110n2

於是我們寫出函式GetSum1。

LL GetSum1(int n,int k)
//n為自由位個數,k為總位數
{
    if (k%2==0)
    {
        if (n%2==0) return 0;
        else
        {
            LL d=-45;
            rep(i,1,n-1) d*=10;
            return d;
        }
    }
    else
    {
        LL d=-1;
        rep(i,1,n) d*=10;
        return d/2;
    }
}

接下來,考慮帶字首的情況。總位數=字首位+自由位。
(1)當總位數為偶數時,字首符號不變,乘上總行數即可。
(2)當總位數為奇數時,字首兩兩相消。

依照以上分析編寫GetSum2。

LL GetSum2(LL prefix,int n)
//prefix為字首,n為自由位個數
{
    int d=0,t=1;
    LL p=prefix,presum=0;
    while (p>0)
    {
        presum+=(p%10)*t;
        p/=10;
        d++;
        t=-t;
    }
    presum*=-t;
    rep(i,1,n) presum*=10;
    LL ret=GetSum1(n,n+d);
    if ((d+n)%2==0) ret+=presum;
    return ret;
}

沿用上例的思路,,再有了上述兩個函式之後,我們繼續將整個區間劃分為若干段,分別利用上述函式求和,這裡不再重複敘述。

小結

通過對問題從簡單到複雜的層層遞進分析,逐步將程式實現,使得一個原本比較複雜的問題輕鬆被解決。程式編寫過程中思路明確,程式模組化合理,每個模組功能明確,並且單獨可以通過check函式進行檢驗。使得出錯的可能性大大降低。

雖然整體程式碼量有所增加,但由於思考的時間減少,程式碼編寫時間甚至還會縮短。