数位dp

数位dp:

/*数位dp模板
int dfs(int i, int s, bool e) {
    if (i==-1) return s==target_s;
    if (!e && ~f[i][s]) return f[i][s];
    int res = 0;
    int u = e?num[i]:9;
    for (int d = first?1:0; d <= u; ++d)
        res += dfs(i-1, new_s(s, d), e&&d==u);
    return e?res:f[i][s]=res;
}
~~f为记忆化数组;

~~i为当前处理串的第i位(权重表示法,也即后面剩下i+1位待填数);

~~s为之前数字的状态(如果要求后面的数满足什么状态,也可以再记一个目标状态t之类,for的时候枚举下t);

~~e表示之前的数是否是上界的前缀(即后面的数能否任意填)。

~~for循环枚举数字时,要注意是否能枚举0,以及0对于状态的影响,有的题目前导0和中间的0是等价的,
  但有的不是,对于后者可以在dfs时再加一个状态变量z,表示前面是否全部是前导0,也可以看是否是首位,
  然后外面统计时候枚举一下位数。It depends.
*/

整体思路就是先把每位数按位分解然后对每位数字分解一个一个遍历找到符合要求的.

例题:CF 55D BEAUTIFUL NUMBERS

题意:

一个美丽数就是可以被他的每一位数字整除

给定一个区间,求美丽数的个数

dp题 状态转移 与dfs结合

递推比较难想

/*
 * 题意:求区间[x , y]中beautiful number的个数,
 * a positive integer number is beautiful if and only
 * if it is divisible by each of its nonzero digits.
分析:一个数能被它的所有非零数位整除,则能被它们的最小公倍数整除,而1到9的最小公倍数为2520,
数位DP时我们只需保存前面那些位的最小公倍数就可进行状态转移,到边界时就把所有位的lcm求出了,
为了判断这个数能否被它的所有数位整除,我们还需要这个数的值,显然要记录值是不可能的,其实我们只
需记录它对2520的模即可,这样我们就可以设计出如下数位DP:dfs(pos,mod,lcm,f),pos为当前
位,mod为前面那些位对2520的模,lcm为前面那些数位的最小公倍数,f标记前面那些位是否达到上限,
这样一来dp数组就要开到19*2520*2520,明显超内存了,考虑到最小公倍数是离散的,1-2520中可能
是最小公倍数的其实只有48个,经过离散化处理后,dp数组的最后一维可以降到48,这样就不会超了。
 */

#include <iostream> 

#include <algorithm> 

#include <cstdio> 
#include <cstring>
#include<cmath>
#include<cstdlib>
#include<map>
using namespace std;
typedef long long ll;
int a[25];
const int N = 1e4 + 5;
const int MOD = 2520;//1~9的lcm为2520
int index[MOD + 10];//记录1~9的最小公倍数
long long dp[25][MOD][48];//dp[i][j]表示到现在有多少个符合条件的i表示当前位置pos,j表示当前状态
int gcd(int a, int b)
{
if (b == 0)return a;
else return gcd(b, a%b);
}
int lcm(int a, int b)
{
return a / gcd(a, b)*b;
}
void init()//更细index数组
{
int i,j;
int num = 0;
for (i = 1; i <= MOD; i++)
{
if (MOD % i == 0)
{
index[i] = num++;//记录1-9的lcm
}
}
}
long long dfs(int pos,int presum,int prelcm,bool limit)
{
if (pos == -1)
return presum%prelcm==0;//当判断
if (prelcm >MOD)
{
return 0;
}
if (!limit&&dp[pos][presum][index[prelcm]]!= -1)
{
return dp[pos][presum][index[prelcm]];
}
int up = limit ? a[pos] : 9;//判断上界
ll tmp = 0;
for (int i = 0; i <= up; i++)
{
int nowlcm, nowsum;
nowsum =(presum * 10 + i)%MOD;
nowlcm = prelcm;
if(i)
nowlcm = lcm(nowlcm,i);
tmp += dfs(pos - 1,nowsum,nowlcm,limit&&i ==up);
}
if (!limit)
dp[pos][presum][index[prelcm]] = tmp;
return tmp;
}
long long solve(long long x)//按位分解 一维数组记录 从最高位记忆化搜索便于判断是否达到顶峰条件

int pos = 0;
while (x)
{
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos-1,0,1,true);
}
int main()
{
long long li, ri;
int t;
init();
scanf("%d",&t);;
memset(dp, -1, sizeof(dp));
while (t--)
{
scanf("%I64d%I64d", &li, &ri);
printf("%I64d\n", solve(ri) - solve(li - 1));
}
return 0;
}

hdoj 2089

不要62和4

#include <iostream> 
#include <algorithm> 
#include <cstdio> 
#include <cstring>
#include<cmath>
#include<cstdlib>
#include<map>
#pragma warning(disable:4996)
using namespace std;
typedef long long ll;
int a[25];
const int N = 1e4 + 5;
const int MOD = 2520;//1~9的lcm为2520
int index[MOD + 10];//记录1~9的最小公倍数
long long dp[25][2];//dp[i][j]表示到现在有多少个符合条件的i表示当前位置pos,j表示当前状态
long long dfs(int pos,int pre,int sta,bool limit)//pre表示上一个的状态
{
if (pos == -1)
return 1;//pos遍历完成的时候return1;
if (!limit&&dp[pos][sta]!= -1)
{
return dp[pos][sta];
}
int up = limit ? a[pos] : 9;//判断上界
ll tmp = 0;
for (int i = 0; i <= up; i++)
{
if (i == 4)continue;
if (pre == 6 && i == 2)continue;
tmp += dfs(pos - 1,i,i==6,limit&&i ==up);
}
if (!limit)
dp[pos][sta] = tmp;
return tmp;
}
long long solve(long long x)//按位分解 一维数组记录 从最高位记忆化搜索便于判断是否达到顶峰条件

int pos = 0;
while (x)
{
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos-1,-1,0,true);
}
int main()
{
long long li, ri;
int t;

while (scanf("%lld%lld", &li, &ri)!=EOF&&(li||ri))
{
memset(dp, -1, sizeof(dp));
printf("%d\n", solve(ri) - solve(li - 1));
}
return 0;
}

hdoj 4734

题目描述:

For a decimal number x with n digits (A  nn-1n-2 ... A  21), we define its weight as F(x) = A  n * 2  n-1 + A  n-1 * 2  n-2 + ... + A  2 * 2 + A  1 * 1. Now you are given two numbers A and B, please calculate how many numbers are there between 0 and B, inclusive, whose weight is no more than F(A).

code:

#include <iostream> 
#include <algorithm> 
#include <cstdio> 
#include <cstring>
#include<cmath>
#include<cstdlib>
#include<map>
#pragma warning(disable:4996)
using namespace std;
typedef long long ll;
int a[25];
int all;
const int N = 1e4 + 5;
const int MOD = 2520;//1~9的lcm为2520
int index[MOD + 10];//记录1~9的最小公倍数
long long dp[25][100000];//dp[i][j]表示到现在有多少个符合条件的i表示当前位置pos,j表示当前状态
int f(int x)
{
if (x == 0) return 0;
int ans = f(x / 10);
return ans * 2 + (x % 10);
}
long long dfs(int pos,int sum,bool limit)//
{
if (pos == -1)
return sum<=all;//pos遍历完成的时候return1;
if (sum > all)
return 0;
if(!limit&&dp[pos][all-sum]!= -1)
{
return dp[pos][all-sum];
}
int up = limit ? a[pos] : 9;//判断上界
ll tmp = 0;
for (int i = 0; i <= up; i++)
{
int newsum = sum+i*(1<<pos);
tmp += dfs(pos - 1,newsum,limit&&i ==up);
}
if (!limit)
dp[pos][all-sum] = tmp;
return tmp;
}
long long solve(long long x)//按位分解 一维数组记录 从最高位记忆化搜索便于判断是否达到顶峰条件

int pos = 0;
while (x)
{
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos-1,0,true);
}
int main()
{
long long li, ri;
int t;
scanf("%d", &t);
int kase = 0;
while (t--)
{
scanf("%lld%lld", &li, &ri);
all = f(li);
memset(dp, -1, sizeof(dp));
printf("Case #%d: %d\n",++kase,solve(ri));
}
return 0;

}

FZU2109

题意:

这题要求统计出【l,r】这个区间内  山数的总数。
山数就是奇数位不小于左右两边的数  的数  如 1919191  45454  1111  这三个都是山数

#include <iostream> 
#include <algorithm> 
#include <cstdio> 
#include <cstring>
#include<cmath>
#include<cstdlib>
#include<map>
#pragma warning(disable:4996)
#include<iostream>
#include<queue>
using namespace std;
int x;
int l, r;
int a[20];
int dp[23][23][2];//i=pos,j=pre,k=sta
int cnt;
int dfs(int pos,int pre,int sta,int zero,bool limit)
{//pre表示前一个数,sta表示是奇数位还是还是偶数位,limit判断是否越界,limit为1是没有达到上界
if (pos == -1)
return 1;
if (!limit&&dp[pos][pre][sta] != -1)//达到上界之后
return dp[pos][pre][sta];
int up=limit?a[pos]:9;
int ans = 0;
for (int i = 0; i <= up; i++)
{
//没有前导0
if (!(i || zero))
ans += dfs(pos - 1, i, !sta, zero || i, limit&&i == up);
//如果是偶数位的同时当前值大于pre
else if (sta && pre <= i)
ans += dfs(pos - 1, i, !sta, zero || i, limit&&i == up);
//如果是奇数位的同时当前项小于pre
else if (!sta && pre >= i)
ans += dfs(pos - 1, i, !sta, zero || i, limit&&i == up);
}
if(!limit)
dp[pos][pre][sta] = ans;
return ans;
}
int solve(int x)
{

int pos = 0;
cnt = 0;
while (x)
{
a[pos++] = x % 10;
x /= 10;
}
return dfs(pos-1,9,0,0,true);
}
int main()
{
int t;
scanf("%d",&t);
while (t--)
{
int i, j, k;
memset(dp, -1, sizeof(dp));
scanf("%d %d",&l,&r);
printf("%d\n",solve(r)-solve(l-1));


}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值