讓我們來寫數位DP吧~

HDU 2089 不要62

限制:
62 不能連號, 不能出現4

思路:
1. 對於當前位數字是 4 有限制,可以直接處理!
2.
pre_num(前一個數字) 有限制;
if(pre_num == 6) dp當前位不能取 2 狀態變化.
else dp數組可以任意取.
所以對於當前位 的 前面位有兩種情況,
但是我們是寫的DFS,所以先對於後面的狀態先更新,所以dp更新的時候對於後一位要記錄dp[][0/1] (0/1代表 pre_num(=6 / !=6 ))

int dp[10][2], a[10];
int DFS(int pos, int sta, bool limit){
    if(pos < 0) return 1;
    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];
    int up = limit ? a[pos] : 9;
    int temp = 0;
    for(int i=0;i<=up;i++){
        if(i == 4) continue;
        if(sta && i == 2) continue;
        temp = temp + DFS(pos-1, i==6, limit && (i == up));
    }
    if(!limit) dp[pos][sta] = temp;
    return temp;
}

int solve(int n){
    int num = 0;
    while(n){
        a[num++] = n % 10;
        n /= 10;
    }
    return DFS(num-1, 0, true);
}

int main(){
    int n, m;
    memset(dp, -1, sizeof(dp));
    while(scanf("%d%d", &n, &m)){
        if(!n && !m) break;
        printf("%d\n", solve(m) - solve(n-1));
    }
    return 0;
}

HDU 4734 F(x);

在區間[0, B]內有多少個 F(x) <= F(A).

F(x) = An * 2n-1 + An-1 * 2n-2 + … + A2 * 2 + A1 * 1.

限制:
1. 先考慮F(x) 的最大值,最高(2^9 - 1) * 9 = 525 * 9

數位DP(大)都是從高位搜下去的,如果按照題意考慮我們持續從最高位搞箇中間值加起來!
dp可以記錄,當前位是多大的 個數。
但是如何轉移呢???對於之前那位,OK,我已經知道之前的和 pre_sum 是多少,
現在我們只要算一下當前pos 滿足 F(A) - pre_sum >= 0的那些 DP 值.
考慮減法;
如果對於當前值就是F(A),那麼之後的權值和是不是<=F(A)就行了,
是不是變成了當前值是temp,那麼之後的權值和是不是<=temp就行了,
那我直接把temp直接記錄,就是對於當前位 <= temp 的數量.

注意:
存數位的數組一些位置無意義但被使用,要初始化!

int dp[11][5000];
int a[11];
int n, m;

int DFS(int pos, int sta, bool limit){
    if(sta < 0) return 0;
    if(pos < 0) return 1;
    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];

    int up = limit ? a[pos] : 9;
    int ans = 0;
    for(int i=0;i<=up;i++)
        ans = ans + DFS(pos-1, sta-(1<<pos)*i, limit && (i == up));
    if(!limit) dp[pos][sta] = ans;
    return ans;
}

int solve(){
    int num = 0;
    int temp = 0;
    while(n){
        temp = temp + (n % 10) * (1<<num);
        n /= 10;
        num++;
    }
    memset(a, 0, sizeof(a));
    num = 0;
    while(m)
    {
        a[num++] = m % 10;
        m /= 10;
    }
    return DFS(num-1, temp, true);
}

int main(){
    int T, cas=1;
    scanf("%d", &T);
    memset(dp, -1, sizeof(dp));
    while(T--){
        scanf("%d%d", &n, &m);
        printf("Case #%d: %d\n", cas++, solve());
    }
    return 0;
}

51nod1009 數字1的數量

1-N的數的1的數量;

數位上的思考:
1. 個位上1的數量,不考慮個位限制,也就是意味着區間[1, 9],num0 = 1;
2. 加一個十位 1的數量,不考慮個/十位限制,也就是意味着區間[1, 99],
num1 = (兩位數, 1 * 10(十位爲1,個位隨意[0, 9]) + 1 * 9(個位爲1, 十位(1, 9))) + 1(個位數, num0 = 1)
3. 再加一個百位 1的數量,不考慮個/十/百位限制,也就是意味着區間[1, 999]
num2 = (三位數, 1 * 100(百位爲1,其餘隨意[0, 99] + 1 * 90(十位爲1, 個位[0, 9], 百位[1, 9]))) + (兩位數, num1)
4. 現在直接改稱 <= 4 位的一個數[1, 9999],
num4 = 1 * 1000 + 1 * 9 * 100 + 1 * 9 * 100 + 1 * 9 * 100 + num2 = 1*1000 + 3*900 + num2.

顯然這還是不夠的,
我們還要考慮,
like 區間[1, 36], 顯然[1, 9],我們現在要知道的是[10, 36], 但是3>=1, 還是沒有限制
like 區間[1, 106], 其實顯然[1, 99]我們都知道了, 我們現在要知道的是[100, 106], 百位==1, 總共有6個, 十位只能是0, 個位1的時候,只能是百位爲1十位是0
like 區間[1, 236], 其實顯然[1, 99]我們都知道了, 我們現在要知道的是[100, 236], 百位==1, 總共有37[0, 36], 個位爲1,百位可以 2 1/2, 個位可以[0, 9]
like 區間[1, 216], 其實顯然[1, 99]我們都知道了, 我們現在要知道的是[100, 216], 百位==1, 總共有17[0, 16], 十位爲1,百位爲1, 個位可以10[0, 9], 但是百位爲1, 個位只能是7[0, 6]
like 區間[1, 2345], 其實顯然[1, 999]我們都知道了, 我們現在要知道[1000, 2345], 千位==1, 總共有346[0, 345],
百位爲 1, 千位可以1/2, 後面就是 46 [0, 45]
總結一下???
對於這裏我們可以考慮特殊情況然後暴力可以得到。
感覺方法太爛!!!
考慮遞推,我們從低到高處理,
答案是累加的過程。
like 區間[1, 36]至36, 對於最高位是1, 都OK就是加[0, 9] 然後考慮尾數1的個數(統籌考慮), 那麼就是 +=num1*3([0,1,2]), 之前的就是高位爲3的情況
like 區間[1, 106]至106, 對於最高位是1, 那麼就是 +=7[0, 06](+尾數+1), 然後加上num2*1(最高位取0)
like 區間[1, 236]至236, 對於最高位是1, 都OK就是加[0, 99], 然後考慮尾數1的個數, 那麼就是 +=num2*3([0,1,2]) + 之前的就是高位爲3的情況
OK!

也可以考慮DFS,考慮每一位的數的貢獻。


CodeForces55D Beautiful numbers

題意:
查詢區間有多少個 整除 自己每位上非0的數

限制:
0無限制、能整除每一位。
X % Y == 0
X * 10 % Y == 0

方案1.[GG]
一開始開了臨時值然後直接dp[pos]巨撒比,明明這一位對於前一位是有特殊關係的,然後直接後一位求出來了,對於前一位的另一個都不滿足了
like -> if(!limit && dp[pos] != -1) return dp[pos];
死於–>轉移

方案2.[GG]
想了開狀態來記錄 [至當前] 能 (%x == 0) 的方案數,但是沒用啊!!!
但是中間寫崩了————對於中間的時候,很撒比地 如果 [(中間值*10 + i % i) != 0] 直接不管了!!
woc??? 你怎麼知道的??? 對於中間的時候 雖然現在不能模i等於0,不代表之後加一些數可以實現膜i等於0吧
死於—>轉移

總結一下————
1.顯然我們是需要中間值的,用來轉移
等等!這個中間好大啊!怎麼優化一下,%(123456789的LCM) 就好了嘛.
2.我們無法在中間判斷這個值是不是不合法!!!NO WAY!!!所以我們還要記錄到個位的時候是不是對所有的位都能膜他等於0
Sooooooooo!!!我們是不是要記錄前面這些位,怎麼記錄————乘起來很棒棒!!!————LCM更加棒棒!!!
直接存 空間複雜度好像很大!!!我們考慮LCM有多少個?(話說怎麼求啊)這些LCM (1*2*3*…*8*9) 的約數嘛, 我們可以離散化一下

方案3.
現在我們數位DP, 每次會記錄一箇中間值temp,一個lcm, 不滿足的話 就是到了最低位 temp % lcm != 0;
那麼我們開 dp[][][]三維就可以剛了!

int mod, hs[3000], xs[55];
void init(){
    mod = 2520;
    int num = 0;
    for(int i=1;i<=mod;i++)
        if(mod % i == 0){
            xs[++num] = i;
            hs[i] = num;
        }
//    printf("%d\n", num);
}

int a[25];
LL dp[25][3000][55];
int LCM(int x, int y){
    return x * y / __gcd(x, y);
}

LL DFS(int pos, int temp, int lcm, bool limit){
    if(pos < 0){
        if(temp % lcm == 0) return 1;
        return 0;
    }
    if(!limit && dp[pos][temp][hs[lcm]] != -1) return dp[pos][temp][hs[lcm]];
    int up = limit ? a[pos] : 9;
    int tmp, tlcm;
    LL ans = 0;
    for(int i=0;i<=up;i++){
        tmp = (temp * 10 + i) % mod;
        if(i){
            tlcm = LCM(lcm, i);
            ans = ans + DFS(pos-1, tmp, tlcm, limit && (i == up));
        }
        else ans = ans + DFS(pos-1, tmp, lcm, limit && (i == up));
    }
    if(!limit) dp[pos][temp][hs[lcm]] = ans;
    return ans;
}

LL solve(LL n){
    int num = 0;
    mem(a, 0);
    while(n){
        a[num++] = n % 10;
        n /= 10;
    }
    return DFS(num-1, 0, 1, true);
}

int main(){
    int T;
    LL l, r;
    init();
    mem(dp, -1);
    scanf("%d", &T);
    while(T--){
        scanf("%lld%lld",&l, &r);
        printf("%lld\n", solve(r) - solve(l-1));
    }
    return 0;
}

HDU3555 Bomb

題意:
1-N到有多少個數是包含”49”的.

思路:
還記得不要62嘛…
emmmmm, 我們算一下不要49…然後哼哼哼…

LL dp[25][2];
int a[25];

LL DFS(int pos, int sta, bool limit){
    if(pos < 0) return 1;
    if(!limit && dp[pos][sta] != -1) return dp[pos][sta];
    int up = limit ? a[pos] : 9;
    LL ans = 0, t;
    for(int i=0;i<=up;i++){
        if(sta && i == 9) continue;
        if(i == 4) t = 1;
        else t = 0;
        ans = ans + DFS(pos-1, t, limit && (i == up));
    }
    if(!limit) dp[pos][sta] = ans;
    return ans;
}

LL solve(LL n){
    int num = 0;
    mem(a, 0);
    while(n){
        a[num++] = n % 10;
        n /= 10;
    }
    return DFS(num-1, 0, true);
}

int main(){
    int T;
    LL n;
    mem(dp, -1);
    scanf("%d", &T);
    while(T--){
        scanf("%lld", &n);
        printf("%lld\n", n - solve(n) + 1);
    }
    return 0;
}

LightOJ 1140

題意:
給出一個區間,求區間內0出現的個數。
思路:
因爲窩比較蠢,在想這個特殊位不就是0嘛,ok,狀態設個 是0/不是0,感覺美滋滋。
但是顯然,這就有一個重大的問題,是的沒錯,對於當前位,123456789這些數字,他的後多少位出現0的個數都沒區別,
但是數位DP,這個DP記錄的值表示的是對於當前位的前面所有情況!!!所以太明顯了這個思路錯的!
所以DP的狀態 需要的是到當前位的一類情況
那麼對於這道題,記錄從首位到當前位零的個數就對了。


LL dp[25][15];
int a[25];

LL DFS(int pos, bool pre_zero, bool limit, int sta){
    if(pos < 0) return (pre_zero ? 1 : (LL)sta);

    if(!limit && !pre_zero && dp[pos][sta]!=-1) return dp[pos][sta];
    int up = limit ? a[pos] : 9;
    LL temp = 0;
    for(int i=0;i<=up;i++){
        if(pre_zero){
            temp += DFS(pos-1, pre_zero&&i==0, limit&&i==up, sta);
        }
        else{
            if(!i) temp += DFS(pos-1, pre_zero, limit && i==up, sta+1);
            else temp += DFS(pos-1, pre_zero, limit && i==up, sta);
        }
    }
    if(!limit && !pre_zero) dp[pos][sta] = temp;
    return temp;
}

LL solve(LL x){
    if(x<0) return 0;
    if(x==0) return 1;
    int num = 0;
    while(x){
        a[num++] = x % 10;
        x /= 10;
    }
    return DFS(num-1, true, true, 0);
}

int main(){
    LL n, m;
    int T, cas = 1;
    memset(dp, -1, sizeof(dp));
    scanf("%d", &T);
    while(T--){
        scanf("%lld%lld", &n, &m);
        printf("Case %d: ", cas++);
        printf("%lld\n", solve(m) - solve(n-1));
    }
    return 0;
}
發佈了799 篇原創文章 · 獲贊 125 · 訪問量 38萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章