1. 程式人生 > >51Nod-1284 2 3 5 7的倍數【數位DP+記憶化搜尋】

51Nod-1284 2 3 5 7的倍數【數位DP+記憶化搜尋】

給出一個數N,求1至N中,有多少個數不是2 3 5 7的倍數。 例如N = 10,只有1不是2 3 5 7的倍數。

Input

輸入1個數N(1 <= N <= 10^18)。

Output

輸出不是2 3 5 7的倍數的數共有多少。

Input示例

10

Output示例

1

問題分析

  這是一個數位DP問題,用記憶化搜尋實現。

程式說明:(無)

題記:(略)

AC的C++語言程式如下:

/* 51Nod-1284 2 3 5 7的倍數 */

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int M2 = 2;
const int M3 = 3;
const int M5 = 5;
const int M7 = 7;
const int N = 20;  // 位數,long long型別不超過20位
int digits[N + 1];
LL dp[N][M2][M3][M5][M7];  // dp[i][m2][m3][m5][m7]-共i位,其中m2-m7分別為2 3 5 7的餘數

/*
 * 引數:
 * pos - 數位位置,即當前處理數的第幾位,從高位開始
 * m2-m7 - 2 3 5 7的餘數
 * limit - 是否為數位上界(最大數字)
 */
LL dfs(int pos, int r2, int r3, int r5, int r7, bool limit)
{
    if(pos == -1) {   // 遞迴邊界,已經列舉結束,則返回1的數量
        if(r2 == 0 || r3 == 0 || r5 == 0 || r7 == 0)
            return 0;
        else
            return 1;
    }
    if(!limit && dp[pos][r2][r3][r5][r7] != -1)  // 已經搜尋過的不再搜尋,直接使用之前的計算結果
        return dp[pos][r2][r3][r5][r7];

    // 計數
    LL ans = 0;
    int maxd = limit ? digits[pos] : 9;  // 列舉數字,如果數字不同則列舉0-9
    for(int i = 0; i <= maxd; i++) {
        ans += dfs(pos - 1, (r2 * 10 + i) % M2, (r3 * 10 + i) % M3, (r5 * 10 + i) % M5, (r7 * 10 + i) % M7, limit && i == maxd);
    }
    if(!limit)
        dp[pos][r2][r3][r5][r7] = ans;

    return ans;
}

// 計算[0,n]中不是2 3 5 7倍數數的數量之和
LL solve(LL n)
{
    int len = 0;
    while(n) {
        digits[len++] = n % 10;
        n /= 10;
    }
    return dfs(len - 1, 0, 0, 0, 0, true);
}

int main()
{
    memset(dp, -1, sizeof(dp));

    LL n;
    while(~scanf("%lld", &n))
        printf("%lld\n", solve(n));

    return 0;
}