数位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;}
希望以上对你有帮助!
- 数位DP小结
- 数位DP小结
- 基础数位DP小结
- 数位DP小结
- 数位DP小结
- 数位DP小结
- 数位dp小结
- 数位DP学习小结
- 数位dp小结
- 数位DP小结
- 【算法笔记】数位dp小结
- 数位DP小结(阶段性)
- (原创)数位DP专题小结--by sgx
- 数位DP专题小结--by sgx
- 数位DP专题小结--by sgx 数位DP专题小结--by sgx
- 数位DP小结_记忆化搜索版
- 数位dp小结(状压的第一次试水)
- 数位dp
- 推荐一篇挺有意思的论文
- 文章标题
- python读取mysql中数据保存至列表
- freemarker layout 模板使用
- 2015-07-23日总结
- 数位DP小结
- 欢迎使用CSDN-markdown编辑器
- html学习
- 杭电ACM2094——产生冠军~~拓扑排序
- Axure入门设计——倒计时设计
- mac 上安装IDEA14 注意事项
- java 实现IP访问量控制
- 今天很高兴!找到了组织,找到了师父!正式加入beetlsql开发,发个博客庆祝下。
- ch2 一切都是对象