您的位置:首页 > 其它

数位dp初等专题小总结

2016-08-01 12:27 323 查看
问题来源于kuangbin数位dp专题。。

数位dp , 记忆化搜索;

所以 他的时间复杂度要平均化

个人喜欢dfs版本的,因为感觉简单

然而专题中的题目已经过期了。。

hdu 2089 不要62

最简单的一种。。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
int dig[8];
int dp[8][10];
int dfs(int len , int pre , int flag){//长度,前面传递的信息,是否受到目标的限制
if(len < 0){
return 1;
}
if(!flag && dp[len][pre] != - 1){//记忆话搜索
return dp[len][pre];
}
int end = flag ? dig[len] : 9;//判断是否受到限制
int ans = 0;
for(int i = 0 ; i <= end ; ++ i){//在遇到非法情况的时候直接斩掉。。当然也可以保存所有的非法情况再用合法的减去。因为这题数据范围小,随意
if(pre == 6 && i == 2){
continue;
}
if(i == 4){
continue;
}
ans += dfs(len - 1 , i , flag && i == end);//结尾的标记
}
if(!flag){
dp[len][pre] = ans;
}
return ans;
}
int solve(int x){
int cnt = 0;
while(x){
dig[cnt ++] = x % 10;
x = x / 10;
}
return dfs(cnt - 1 , 0 , 1);
}
int main(){
int l , r;
memset(dp , -1 , sizeof(dp));
while(~scanf("%d%d" , &l , &r)){
if(l == 0 && r == 0){
break;
}
printf("%d\n" , solve(r) - solve(l - 1));
}
return 0;
}


hdu 3652 B-number

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int dig[10];
int dp[10][13][3];//位数,总和,是否出现13
int dfs(int len , int pre1 , int pre2 , int flag){
if(len < 0){
return pre1 == 0 && pre2 == 2;
}
if(!flag && dp[len][pre1][pre2] != -1){
return dp[len][pre1][pre2];
}
int end = flag ? dig[len] : 9;
int ans = 0;
for(int i = 0 ; i <= end ; ++ i){
if(pre2 == 1 && i == 3){
ans += dfs(len - 1 , (pre1 * 10 + i) % 13 , 2 , flag && i == end);
}
else if(pre2 == 2){
ans += dfs(len - 1 , (pre1 * 10 + i) % 13 , 2 , flag && i == end);
}
else if(i == 1){
ans += dfs(len - 1 , (pre1 * 10 + i) % 13 , 1 , flag && i == end);
}
else{
ans += dfs(len - 1 , (pre1 * 10 + i) % 13 , 0 , flag && i == end);
}
}
if(!flag){
dp[len][pre1][pre2] = ans;
}
return ans;
}

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

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


hdu 3555 bomb

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define ULL unsigned long long
int dig[64];
ULL dp[64][3];
ULL dfs(int len , int pre , int flag){
if(len < 0){
return pre == 2;
}
if(!flag && dp[len][pre] != -1){
return dp[len][pre];
}
int end = flag ? dig[len] : 9;
ULL ans = 0;
for(int i = 0 ; i <= end ; ++ i){
if(pre == 1 && i == 9){
ans += dfs(len - 1 , 2 , flag && i == end);
}
else if(pre == 2){
ans += dfs(len - 1 , 2 , flag && i == end);
}
else if(i == 4){
ans += dfs(len - 1 , 1 , flag && i == end);
}
else{
ans += dfs(len - 1 , 0 , flag && i == end);
}
}
if(!flag){
dp[len][pre] = ans;
}
return ans;
}

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

int main(){
int T;
scanf("%d" , &T);
memset(dp , -1 , sizeof(dp));
while(T --){
ULL n;
scanf("%I64u" , &n);
printf("%I64u\n" , solve(n));
}
return 0;
}


codeforces 55D 这真是极好的题啊。。 对于离散化的只是又熟悉了一遍。。2520是123456789的最小公倍数,然而0-2520中许多数字是不需要的。。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
int dig[20];
LL dp[20][2520][50];
int all[2521];//离散化
int gcd(int x , int y){
if(x > y){
swap(x , y);
}
return x == 0 ? y : gcd(y % x , x);
}
void init(){
int tot = 0;
for(int i = 1 ; i <= 2520 ; ++ i){
if(2520 % i == 0){
all[i] = tot ++;
}
}
}

LL dfs(int len , int pre1 , int pre2 , int flag){
if(len < 0){
return pre1 % pre2 == 0;
}
if(!flag && dp[len][pre1][all[pre2]] != -1){
return dp[len][pre1][all[pre2]];
}
int end = flag ? dig[len] : 9;
LL ans = 0;
for(int i = 0 ; i <= end ; ++ i){
ans += dfs(len - 1 , (pre1 * 10 + i) % 2520 , i == 0 ? pre2 : pre2 * i / gcd(pre2 , i) , flag && i == end);
}
if(!flag){
dp[len][pre1][all[pre2]] = ans;
}
return ans;
}

LL solve(LL x){
int cnt = 0;
while(x){
dig[cnt ++] = x % 10;
x = x / 10;
}
return dfs(cnt - 1 , 0 , 1 , 1);
}
int main(){
int T;
scanf("%d" , &T);
init();
memset(dp , -1 , sizeof(dp));
while(T --){
LL l , r;
scanf("%I64d%I64d" , &l , &r);
printf("%I64d\n" , solve(r) - solve(l - 1));
}
return 0;
}


poj 3252 Round Numbers

前导零这种东西用zero标记一下就结束了。。 讲道理pre1的效果中已经有了。只要有规则,任何进制都是用数位dp极好的。。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int dig[35];
int dp[35][35][35];
int dfs(int len , int pre0 , int pre1 , int flag , int zero){
if(len < 0){
return pre1 <= pre0;
}
if(!flag && dp[len][pre1][pre0] != -1){
return dp[len][pre1][pre0];
}
int end = flag ? dig[len] : 1;
int ans = 0;
for(int i = 0 ; i <= end ; ++ i){
if(i == 0)
ans += dfs(len - 1 , zero ? 0 : pre0 + 1 , pre1 , flag && i == end , zero && !i);
else
ans += dfs(len - 1 , pre0 , pre1 + 1 , flag && i == end , 0);
}
if(!flag){
dp[len][pre1][pre0] = ans;
}
return ans;
}

int solve(int x){
int cnt = 0;
while(x){
dig[cnt ++] = (x & 1);
x >>= 1;
}
return dfs(cnt - 1 , 0 , 0 , 1 , 1);
}

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


hdu 3709 Balanced Number

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
int dig[20];
LL dp[20][20][2000];
LL dfs(int len , int pre , int mid , int flag){
if(len < 0){
return pre == 0;
}
if(!flag && dp[len][mid][pre] != -1){
return dp[len][mid][pre];
}
int end = flag ? dig[len] : 9;
LL ans = 0;
for(int i = 0 ; i <= end ; ++ i){
ans += dfs(len - 1 , (len - mid) * i + pre , mid , flag && i == end);
}
if(!flag){
dp[len][mid][pre] = ans;
}
return ans;
}
LL solve(LL x){
int cnt = 0;
while(x){
dig[cnt ++] = x % 10;
x = x / 10;
}
LL ans = 0;
for(int i = 0 ; i < cnt ; ++ i){
ans += dfs(cnt - 1 , 0 , i , 1);
}
return ans - cnt + 1;
}
int main(){
int T;
scanf("%d" , &T);
memset(dp , -1 , sizeof(dp));
while(T --){
LL l , r;
scanf("%I64d%I64d" , &l , &r);
printf("%I64d\n" , solve(r) - solve(l - 1));
}
return 0;
}


hdu 4734 F(x)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
int dig[15];
LL fa;
LL dp[15][10240];
LL make(LL x){
LL now = 1;
LL ans = 0;
while(x){
ans += x % 10 * now;
x = x / 10;
now = (now << 1);
}
return ans;
}

LL dfs(int len , int pre , int flag){
if(len < 0){
return pre >= 0;
}
if(!flag && dp[len][pre] != -1){
return dp[len][pre];
}
int end = flag ? dig[len] : 9;
LL ans = 0;
LL now = (1 << len);
for(int i = 0 ; i <= end ; ++ i){
if(pre - i * now < 0){
break;
}
ans += dfs(len - 1 , pre - i * now , flag && i == end);
}
if(!flag){
dp[len][pre] = ans;
}
return ans;
}

LL solve(LL x){
int cnt = 0;
while(x){
dig[cnt ++] = x % 10;
x = x / 10;
}
return dfs(cnt - 1 , fa , 1);
}

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


spoj Balanced Numbers

一开始智障一样的想用数组维护每个数字的奇偶性,发现要20*3的10次方,要爆炸。。然后就想到3进制了。。愉快的第一发T了。。原来是I64d。。欸。。好气啊。。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
#define LL long long
LL dp[20][81 * 81 * 9 + 10];
int dig[20];
LL Pow(int a , int b){
LL ans = 1;
while(b){
if(b & 1){
ans *= a;
}
a = a * a;
b >>= 1;
}
return ans;
}
int change(int pre , int i){
int test = pre;
int cnt = 0;
while(pre){
if(cnt == i){
break;
}
pre = pre / 3;
cnt ++;
}
int fin = pre % 3;
if(fin == 2){
return test - Pow(3 , i);
}
else{
return test + Pow(3 , i);
}
}

int check(int x){
if(x == 0){
return 0;
}
int cnt = 0;
while(x){
int test = x % 3;
x = x / 3;
if(test == 0){
cnt ++;
continue ;
}
if(cnt & 1){
if(test & 1){
return 0;
}
}
else{
if(!(test & 1)){
return 0;
}
}
cnt ++;
}
return 1;
}
LL dfs(int len , int pre , int flag , int zero){
if(len < 0){
return check(pre);
}
if(!flag && dp[len][pre] != -1){
return dp[len][pre];
}
int end = flag ? dig[len] : 9;
LL ans = 0;
for(int i = 0 ; i <= end ; ++ i){
if(zero && i == 0){
ans += dfs(len - 1 , 0 , flag && i == end , 1);
}
else{
ans += dfs(len - 1 , change(pre , i) , flag && i == end , 0);
}
}
if(!flag){
dp[len][pre] = ans;
}
return ans;
}

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

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


HDU 4507 吉哥系列故事——恨7不成妻 这是一道很好的题目,知道做到这道题才算是理解了数位dp的原理。数位dp每一位保存的代表的是具有相同特征的东西。而且和其他位置保存的值是互斥的。所以直接返回平方的做法是错误的

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define LL long long
const LL mod = 1e9 + 7;
struct all{
LL cnt;
LL sum1;
LL fin;
all(){};
all(LL a , LL b , LL c){
cnt = a;
sum1 = b;
fin = c;
}
}dp[20][10][10];
int dig[20];
LL loc[20];
all dfs(int len , int pre1 , int pre2 , int flag){
if(len < 0){
all ans = all(0 , 0 , 0);
ans.cnt = (pre1 != 0 && pre2 != 0);//??????
return ans;
}
if(!flag && dp[len][pre1][pre2].cnt != -1){
return dp[len][pre1][pre2];
}
int end = flag ? dig[len] : 9;
all ans = all(0 , 0 , 0);
for(int i = 0 ; i <= end ; ++ i){
if(i == 7){
continue;
}
all tmp = dfs(len - 1 , (pre1 * 10 + i) % 7 , (pre2 + i) % 7 , flag && i == end);//????????
ans.cnt = (ans.cnt + tmp.cnt) % mod;
ans.sum1 = (ans.sum1 + (tmp.sum1 + ((i * loc[len]) % mod * tmp.cnt) % mod) % mod) % mod;
ans.fin = (ans.fin + (tmp.fin + (((i * loc[len]) % mod * (i * loc[len]) % mod) % mod * tmp.cnt % mod + (i * loc[len]) % mod * tmp.sum1 % mod * 2 % mod) % mod) % mod) % mod;
}
if(!flag){
dp[len][pre1][pre2] = ans;
}
return ans;
}

LL solve(LL x){
int cnt = 0;
while(x){
dig[cnt ++] = x % 10;
x = x / 10;
}
return dfs(cnt - 1 , 0 , 0 , 1).fin;
}
void init(){
loc[0] = 1;
for(int i = 1 ; i <= 19 ; ++ i){
loc[i] = loc[i - 1] * 10 % mod;
}
}
int main(){
int T;
scanf("%d" , &T);
memset(dp , -1 , sizeof(dp));
init();
while(T --){
LL l , r;
scanf("%I64d%I64d" , &l , &r);
//cout<<solve(9)<<endl;
printf("%I64d\n" , (solve(r) - solve(l - 1) + mod) % mod);
}
return 0;
}


hdu 4352 XHXJ’s LIS

最后感觉就像做模拟。。。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define LL long long
int k;
int dig[64];
LL dp[64][1 << 10][11];
int change(int x , int i){
int t = x;
for(int j = i ; j <= 10 ; ++ j){
if(t & (1 << j)){
t = (t ^ (1 << j));
break;
}
}
return t | (1 << i);
}
int check(int x){
int cnt = 0;
while(x){
if(x & 1){
++ cnt;
}
x >>= 1;
}
if(cnt == k){
return 1;
}
else{
return 0;
}
}
LL dfs(int len , int pre , int flag , int zero){
if(len < 0){
return check(pre);
}
if(!flag && dp[len][pre][k] != -1){
return dp[len][pre][k];
}
int end = flag ? dig[len] : 9;
LL ans = 0;
for(int i = 0 ; i <= end ; ++ i){
if(zero && i == 0){
ans += dfs(len - 1 , 0 , flag && i == end , 1);
}
else{
ans += dfs(len - 1 , change(pre , i) , flag && i == end , 0);
}
}
if(!flag){
dp[len][pre][k] = ans;
}
return ans;
}
LL solve(LL x){
int cnt = 0;
while(x){
dig[cnt ++] = x % 10;
x = x / 10;
}
return dfs(cnt - 1 , 0 , 1 , 1);
}
int main(){
int T;
scanf("%d" , &T);
int cas = 0;
memset(dp , -1 , sizeof(dp));
while(T --){
LL l , r;
scanf("%I64d%I64d%I64d" , &l , &r , &k);
printf("Case #%d: %I64d\n" , ++ cas , solve(r) - solve(l - 1));
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  数位dp