码迷,mamicode.com
首页 > 其他好文 > 详细

数位DP小结

时间:2015-07-23 23:45:51      阅读:192      评论:0      收藏:0      [点我收藏+]

标签:

如果以下错的地方,谢谢提出。

1.HDU - 2089 不要62

解题思路:这题的限制条件是不能出现4,和数字中不能包含62,那么就对这两个进行特判即可

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 12
int n, m;
int dp[N][N];
int data[N];
//zero_flag是前导零的标记,为0时表示有前导0
//border_flag时边界标记
//pos表示当前位
//pre_num表示前一个数
int dfs(int zero_flag, int border_flag, int pos, int pre_num) {

    if (pos == -1)
        return 1;
    if (!border_flag && dp[pre_num][pos] != -1)
        return dp[pre_num][pos]; 

    int ans = 0;
    int end = border_flag ? data[pos]: 9;
    for (int i = 0; i <= end; i++) {
        if(!(zero_flag || i))
            ans += dfs(0, 0, pos - 1, i);
        else if((pre_num == 6 && i == 2) || (i == 4))
            continue;
        else
            ans += dfs(1, border_flag && (i == data[pos]), pos - 1, i);
    } 

    if(!border_flag)
        dp[pre_num][pos] = ans;

    return ans;
}

int solve(int num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(0, 1, cnt-1, 0);
}

void init() {
    memset(dp, -1, sizeof(dp));
}

int main() {
    init();
    while (scanf("%d%d", &n, &m) != EOF && n + m) {
        printf("%d\n", solve(m) - solve(n - 1));
    }
    return 0;
}

2.HDU - 3555 Bomb

题目大意:求出范围內,数字中含有数字49的有多少个

解题思路:这题比上一题难了一点,上一题只要出现4或者发现连续的62就可以continue掉了,但是这题不一样,出现49后表示后面所有的数字都是符合要求的,也就是说,在pos,pre_num的情况下,会出现两种情况,符合要求的和不符合要求的,所以要再上一题的情况下再加一维,表示是否是符合条件的

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 70
ll n;
ll dp[N][12][2];
int data[N];

ll dfs(int zero_flag, int border_flag, int pre_num, int pos, int flag) {

    //枚举完了,还要判断一下是否是符合要求的
    if (pos == -1 )
        return flag;

    if (!border_flag && dp[pos][pre_num][flag] != -1) {
        return dp[pos][pre_num][flag];
    }

    ll ans = 0;
    int end = border_flag ? data[pos] : 9;

    for (int i = 0; i <= end; i++) {
        if(!(zero_flag || i))
            ans += dfs(0, border_flag && (i == end), i, pos - 1, 0);
        else if (pre_num == 4 && i == 9)
            ans += dfs(1, border_flag && (i == end), i, pos - 1, 1);
        else
            ans += dfs(1, border_flag && (i == end), i, pos - 1, flag);
    }

    if (!border_flag)
        dp[pos][pre_num][flag] = ans;

    return ans;
}

ll solve(ll num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(0, 1, 11, cnt - 1, 0);
}

void init() {
    memset(dp, -1, sizeof(dp));
}

int main() {
    init();
    int test;
    scanf("%d", &test);
    while (test--) {
        scanf("%lld", &n);
        printf("%lld\n", solve(n));
    }
    return 0;
}

3.FZU - 2109 Mountain Number

题目大意:奇数位的数要大于等于两边的数

解题思路:在枚举的时候特判一下就可以了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 12
int l, r;
int data[N];
int dp[N][N][2];

int dfs(int zero_flag, int border_flag, int pre_num, int pos, int even_flag) {

    if (pos == -1)
        return 1;

    if (!border_flag && dp[pos][pre_num][even_flag] != -1)
        return dp[pos][pre_num][even_flag];

    int ans = 0;
    int end = border_flag ? data[pos] : 9;

    for (int i = 0; i <= end; i++) {
        if (! (zero_flag || i)) {
            ans += dfs(0, 0, 10, pos - 1, 1);
        }
        else if (!even_flag && i >= pre_num) {
            ans += dfs(1, border_flag && (i == end), i, pos - 1, even_flag ^ 1);
        }
        else if(even_flag && i <= pre_num) {
            ans += dfs(1, border_flag && (i == end), i, pos - 1, even_flag ^ 1);
        }
    }

    if(!border_flag)
        dp[pos][pre_num][even_flag] = ans;

    return ans;
}

int solve(int num) {
    int cnt = 0;
    while(num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(0, 1, 10, cnt - 1, 1);
}

void init() {
    memset(dp, -1, sizeof(dp));
}

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

4.HYSBZ - 1026 windy数

中文题
解题思路:还是比较简单的,直接在枚举时特判就好了

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#define ll long long
#define N 70
ll n, m;
int data[N];
ll dp[N][14];

ll dfs(int zero_flag, int border_flag, int pre_num, int pos) {
    if (pos == -1)
        return 1;

    if (!border_flag && dp[pos][pre_num] != -1)
        return dp[pos][pre_num];

    ll ans = 0;
    int end = border_flag ? data[pos] : 9;
    for (int i = 0; i <= end; i++) {
        if (!(zero_flag || i))
            ans += dfs(0, 0, 13, pos - 1);
        else if (abs(pre_num - i) >= 2)
            ans += dfs(1, border_flag && (i == end), i, pos - 1);
    }

    if (!border_flag)
        dp[pos][pre_num] = ans;

    return ans;
}

ll solve(ll num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(0, 1, 13, cnt -1);
}

void init() {

    memset(dp, -1, sizeof(dp));
}
int main() {
    init();
    while (scanf("%lld%lld", &n, &m) != EOF) {
        printf("%lld\n", solve(m) - solve(n - 1));
    }
    return 0;
}

以下的几题是需要思考下的

5.HDU - 4734 F(x)

解题思路:预先处理一下F(A),然后将处理完后的FA作为参数传入
设dp[pos][num]为扫描到了第pos位,后面的数位按照要求计算后,满足小于等于num的有多少个

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 12
const int S = (1 << 9) * 10;

int data[N], pow2[N];
int A, B;
int dp[N][S];

int dfs(int border_flag, int pos, int num) {

    if (pos == -1) 
        return num >= 0;

    if (num < 0)
        return 0;

    if (!border_flag && dp[pos][num]  != -1) 
        return dp[pos][num];

    int end = border_flag ? data[pos] : 9;
    int ans = 0;
    for (int i = 0; i <= end; i++) 
        ans += dfs(border_flag && (i == end), pos - 1, num - pow2[pos] * i);

    if (!border_flag)
        dp[pos][num] = ans;

    return ans;
}


int solve(int num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }

    int cnt2 = 0;
    int FA = 0;
    while(A) {
        FA += (A % 10) * pow2[cnt2++];
        A /= 10;
    }

    return dfs(1, cnt - 1, FA);
}


void init() {
    pow2[0] = 1;
    for (int i = 1; i < N; i++)
        pow2[i] = pow2[i - 1] << 1;
    memset(dp, -1, sizeof(dp));
}

int main() {

    init();
    int test, cas = 1;
    scanf("%d", &test);
    while (test--) {
        scanf("%d%d", &A, &B);
        printf("Case #%d: %d\n", cas++, solve(B));
    }
    return 0;
}

6.HDU - 4352 XHXJ’s LIS

题目大意:求一个范围內,符合最长上升子序列的长度为k的数字有多少个(1个数可以看成一个序列)

解题思路:看到最长上升子序列,应该想到的是nlog2n的算法,但是,如果实现呢
首先,数字上的数的范围在0-9,可以想到状态压缩,用状态压缩来代替栈
这里的话,多处了个状态,而且还有个限制k,所以可以将dp[pos][state][k]表示位当前枚举到第pos位,前面的位的最长上升子序列状态为state,还需要在state状态上添加数字,使得最长上升子序列的长度再加k的情况有多少种
这样就可以计算了,具体可以先参考上一题

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define N 30
#define S (1<<11)
#define K 12
long long l, r;
//dp[i][j][k]表示枚举到了第i位,状态为j,子序列长度还需要+k才能满足需求的有多少个
long long dp[N][S][K];
int k, data[N];

//zero_flag是前导零的标记,board_flag表示边界标记,state表示状态,len表示还需要几个, high是state下的最高位是哪一位
long long dfs(int zero_flag, int board_flag, int pos, int state, int len, int high) {

    //如果枚举完了,判断一下长度是否符合
    if (pos == -1)
        return len == 0;

    //如果长度超过k了
    if (len < 0)
        return 0;

    if (!board_flag && dp[pos][state][len] != -1)
        return dp[pos][state][len];

    int end = board_flag ? data[pos] : 9;
    long long ans = 0;
    for (int i = 0; i <= end; i++) {
        if(!(zero_flag || i))
            ans += dfs(0, board_flag && (i == end), pos - 1, 0, k, -1);
        else if (state & (1 << i)) //如果当前这个数状态里面已经有了
            ans += dfs(1, board_flag && (i == end), pos - 1, state, len, high);
        else {
            if(i > high) //如果枚举得大于最高位,那么序列长度+1,最高位变成i
                ans += dfs(1, board_flag && (i == end), pos - 1, state | (1 << (i)), len - 1, i);
            else { //否则按nlogn的方法
                int t = 0, cnt = 0;
                for(int j = i + 1; j <= 9; j++) {
                    if(state & (1 << j)) {
                        if(!cnt)
                            t = j;
                        cnt++;
                    }
                }
                //如果最高位被改变了
                if(cnt == 1)
                    ans += dfs(1, board_flag && (i == end), pos - 1, (state ^ (1 << t)) | (1 << i), len, i);
                else
                    ans += dfs(1, board_flag && (i == end), pos - 1, (state ^ (1 << t)) | (1 << i), len, high);
            }
        }
    }

    if (!board_flag) {
        dp[pos][state][len] = ans;
    }

    return ans;
}

long long solve(long long num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(0, 1, cnt - 1, 0, k, -1);
}

void init() {
    memset(dp, -1, sizeof(dp));
}

int main() {
    int test;
    int cas = 1;
    scanf("%d", &test);
    init();

    while (test--) {
        scanf("%lld%lld%d", &l, &r, &k);
        printf("Case #%d: %lld\n", cas++, solve(r) - solve(l - 1));
    }
    return 0;
}

7.HDU - 3709 Balanced Number

题目大意:平衡数表示的是,把一个数的某一位当成平衡点,然后将这个数当成天平,平衡点左右两边的数的计算结果相等,就算一个平衡数,问所给区间內有多少个平衡数

解题思路:平衡数的支点只有一个,因为如果支点左移或者右移的话,数是不可能平衡的。
由此我们可以暴力枚举以每一位当平衡点的情况
枚举数的时候,可以将平衡点左边的数当成正数,右边的数当成负数,如果最后累加的和为0的话,就表示该数平衡了

#include <cstdio>
#include <cstring>

#define N 20
#define S 2000
#define ll long long
ll dp[N][N][S];
ll x, y;
ll data[N];

ll dfs(int border_flag, int pos, int pivot, int sum) {
    if(sum < 0)
        return 0;

    if (pos == -1) {
        return sum == 0;
    }

    if(!border_flag && dp[pos][pivot][sum] != -1)
        return dp[pos][pivot][sum];

    ll ans = 0;
    int end = border_flag ? data[pos] : 9;

    for(int i = 0; i <= end; i++)
        ans += dfs(border_flag && (i == end), pos - 1, pivot, sum + (pos - pivot) * i);

    if(!border_flag)
        dp[pos][pivot][sum] = ans;

    return ans;
}

ll solve(ll num) {
    if(num == -1)
        return 0;
    if(num == 0)
        return 1;

    int cnt = 0;
    while(num) {
        data[cnt++] = num % 10;
        num /= 10;
    }

    ll ans = 0;
    for(int i = 0; i < cnt; i++)
        ans += dfs(1, cnt - 1, i, 0);

    return ans - cnt + 1;
}

int main() {
    int test;
    scanf("%d", &test);
    memset(dp, -1, sizeof(dp));
    while (test--) {
        scanf("%lld%lld", &x, &y);
        printf("%lld\n", solve(y) -  solve(x - 1));
    }
    return 0;
}

8.CodeForces - 55D Beautiful numbers

题目大意:问一个范围內有多少个数满足,该数能被每一位上的数整除

解题思路:如果k能被a和b整除,那么k也能被lcm(a,b)整除,lcm(2-9)的结果是2520,也就是说,如果能除尽2520的,那么必然也能够被2-9的数除尽,所以余数的保留可以用余2520表示
又因为下一个余数是上一个的余数* 10 + i,所以余2520可以变成余252,最后一位再特判一下就好了
设dp[pos][lcm][mod]为扫描到了第pos位了,前面的所有位的数的lcm的结果为lcm,余数位mod的情况下符合要求的平衡数有多少个
因为能被2520除尽的数不会超过50个,所以可以用一个数组进行映射

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 20
#define ll long long

ll dp[N][50][252];
ll l, r;
int data[N];
int Index[2521];

void init() {
    memset(dp, -1, sizeof(dp));
    int cnt = 0;
    for (int i = 1; i <= 2520; i++)
        if (2520 % i == 0)
            Index[i] = cnt++;
}
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

ll dfs(int border_flag, int pos, int lcm, int sum) {
    if(pos == -1)
        return sum % lcm == 0;

    if(!border_flag && dp[pos][Index[lcm]][sum] != -1)
        return dp[pos][Index[lcm]][sum];

    ll ans = 0;
    int end = border_flag ? data[pos] : 9;
    for(int i = 0; i <= end; i++) {
        int t = lcm;
        if(i)
            t = lcm / gcd(lcm, i) * i;
        int tt = sum * 10 + i;
        if(pos)
            tt %= 252;
        ans += dfs(border_flag && (i == end), pos - 1, t, tt);
    }

    if(!border_flag)
        dp[pos][Index[lcm]][sum] = ans;
    return ans;
}

ll solve(ll num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }

    return dfs(1, cnt - 1, 1, 0);
}

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

9.HDU - 4507 吉哥系列故事――恨7不成妻

解题思路:这题比较难,要记录的状态比较多
首先了解一下(a + b) ^2 = a ^2 + b ^ 2 + 2 * a * b
设sum为后缀和,sum2为后缀平方和,num为满足要求的数的数量

当枚举到第pos位,枚举数为i时,就可以更新一下有pos位的数,且以i开头的,符合要求的有几个,后缀和,后缀平方和

以下边看代码边理解比较好理解

设tmp为dfs所得的结果,ans为要返回的结果
数量的话就num加一下就好了,关键是后缀和sum和后缀平方和sum2怎么更新
后缀和相对比较简单
tmp.sum += (pow(10,pos) * i * tmp.num + tmp.sum) 因为有tmp.num个后缀,每个后缀都要加上pow(10,pos) * i,然后再加上本身的后缀和,既是更新的后缀和了

根据上面的公式,后缀平方和
ans.sum2 += pow(10,pos) * pow(10,pos) * i * i *tmp.num(a ^ 2)
ans.sum2 += 2 * pow(10,pos) * i * tmp.sum(2 * a * b,因为有tmp.num个,而tmp.num个的后缀和刚好是tmp.sum)
ans.sum2 += tmp.sum2
既是更新的后缀平方和
记得取余

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 30
#define ll long long
const int mod = 1e9+7;
//sum为后缀和,sum2为后缀平方和,num为数量
struct Node {
    ll num, sum, sum2;
    Node() {
        num = sum = sum2 = 0;
    }
}dp[N][N][N];

int data[N];
ll pow10[N];
ll l, r;

Node dfs(int board_flag, int pos, int sum_f, int mod_f) {
    Node ans, tmp;

    if (pos == -1) {
        ans.num = (sum_f && mod_f);
        return ans;
    }

    if (!board_flag && dp[pos][sum_f][mod_f].num != -1)
        return dp[pos][sum_f][mod_f];

    int end = board_flag ? data[pos] : 9;

    for (int i = 0; i <= end; i++) {
        if (i == 7)
            continue;

        tmp = dfs(board_flag && (i == end), pos - 1, (sum_f + i) % 7, (mod_f * 10 + i) % 7);

        ans.num = (ans.num + tmp.num ) % mod;
        ans.sum = (ans.sum + (pow10[pos] * i % mod * tmp.num + tmp.sum) ) % mod;
        ans.sum2 = (ans.sum2 + (pow10[pos] * pow10[pos] % mod * i * i % mod * tmp.num) + (2 * pow10[pos] * i % mod * tmp.sum) % mod + tmp.sum2) % mod; 
    }
    if(!board_flag)
        dp[pos][sum_f][mod_f] = ans;

    return ans;
}

ll solve(ll num) {
    int cnt = 0;
    while (num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(1, cnt - 1, 0, 0).sum2;

}
void init() {
    memset(dp, -1, sizeof(dp));
    pow10[0] = 1;
    for(int i = 1; i < 20; i++)
        pow10[i] = (pow10[i - 1] * 10 ) % mod;

}

int main() {
    init();
    int test;
    scanf("%d", &test);
    while (test--) {
        scanf("%lld%lld", &l, &r);
        printf("%lld\n", (solve(r) - solve(l - 1) + mod) % mod);
    }
    return 0;
}

漏了一题。。。
HDU - 3652 B-number

这题的话就不写题解了,相信经过了上面这几道题,这题水题以不再话下了

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 15
int n;
int data[N], dp[N][N][N][2];

int dfs(int zero_flag, int border_flag, int pre_num, int pos, int mod, int flag) {

    if (pos == -1) {
        if (flag && !mod)
            return 1;
        return 0;
    }

    if (!border_flag && dp[pos][mod][pre_num][flag] != -1)
        return dp[pos][mod][pre_num][flag];

    int end = border_flag ? data[pos] : 9;
    int ans = 0;
    for (int i = 0; i <= end; i++) {
        if (pre_num == 1 && i == 3) {
            ans += dfs(1, border_flag && (i == end), i, pos - 1, (mod * 10 + i) % 13, 1);
        }
        else 
            ans += dfs(1, border_flag && (i == end), i, pos - 1, (mod * 10 + i) % 13, flag);
    }

    if(!border_flag)
        dp[pos][mod][pre_num][flag] = ans;

    return ans;
}

int solve(int num) {
    int cnt = 0;
    memset(dp, -1, sizeof(dp));
    while(num) {
        data[cnt++] = num % 10;
        num /= 10;
    }
    return dfs(0, 1, 0, cnt - 1, 0, 0);
}

int main() {
    while(scanf("%d", &n) != EOF ) {
        printf("%d\n", solve(n));
    }
    return 0;
}

希望以上对你有帮助!

版权声明:本文为博主原创文章,未经博主允许不得转载。

数位DP小结

标签:

原文地址:http://blog.csdn.net/l123012013048/article/details/47030803

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!