[bzoj 1026] [SCOI2009]windy數:數位DP

題意:區間[a, b]裏有多少個不含前導0的十進制整數任意相鄰兩位之差的絕對值不小於2?(1<=a<=b<=2,000,000,000,a, b是整數)

前幾天做XJOI模擬賽中有關字典序的一道題,發現可以按照某種順序從高位向低位統計。十進制數之間的比較,把前導0補齊,實際上就是字符串的比較。

舉例,如果允許前導0,小於34023的自然數可以這樣生成(下劃線表示0~9間任意數碼):
0 _ _ _ _
1 _ _ _ _
2 _ _ _ _
3 0 _ _ _
3 1 _ _ _
3 2 _ _ _
3 3 _ _ _
3 4 0 0 _
3 4 0 1 _
3 4 0 2 0
3 4 0 2 1
3 4 0 2 2

有了這個背景,回到本題。顯然,我們可以統計[1, b+1)和[1, a)內的結果,再作差。預處理,設f[i][j]爲長度爲(i+1)位十進制串(允許前導0)中有多少個符合“任意相鄰兩位之差的絕對值不小於2”。統計[1, x)內的結果時,先統計位數小於x的,再統計最高位小於x的,然後按照上面所述順序,統計高k位與x相同的,如果x的高k位已不符合Windy數的定義,須及時退出。

#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
const int MAX_N = 10;
int f[MAX_N][10], g[MAX_N];

int cal(char* s, int n)
{
    int ans = n > 1 ? g[n-2] : 0;
    for (int i = 1; i < s[0]-'0'; ++i) // 最高位
        ans += f[n-1][i];
    for (int i = 1; i < n; ++i) { // s[0..i)相同
        int c2 = s[i-1] - '0', c1 = s[i] - '0';
        for (int j = 0; j < c1; ++j)
            if (abs(j-c2) >= 2)
                ans += f[n-i-1][j];
        if (abs(c2-c1) < 2)
            break;
    }
    return ans;
}

int main()
{
    char sa[11], sb[11];
    int a, b;
    scanf("%d %d", &a, &b);
    ++b;
    int la = sprintf(sa, "%d", a), lb = sprintf(sb, "%d", b);

    for (int i = 0; i < 10; ++i)
        f[0][i] = 1;
    for (int i = 1; i < lb; ++i)
        for (int j = 0; j < 10; ++j)
            for (int k = 0; k < 10; ++k)
                if (abs(k-j) >= 2)
                    f[i][j] += f[i-1][k];
    g[0] = 9;
    for (int i = 1; i < lb; ++i) {
        g[i] = g[i-1];
        for (int j = 1; j < 10; ++j)
            g[i] += f[i][j];
    }

    printf("%d\n", cal(sb, lb) - cal(sa, la));

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