数位DP小结

来源:互联网 发布:linux 重启命令卡住 编辑:程序博客网 时间:2024/06/11 03:56

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

1.HDU - 2089 不要62

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

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;#define N 12int 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 70ll 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 12int 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 70ll 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 12const 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 12long 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 longll 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 longll 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 longconst 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 15int 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;}

希望以上对你有帮助!

0 0
原创粉丝点击