@myecho
2019-03-18T15:45:49.000000Z
字数 3093
阅读 819
动态规划
题意给出1-N之间所有不含49的个数
/*
题意:求1~N中含有数字49的个数 1 <= N <= 2^63-1
方法:数位DP
dp[len][0] 代表长度为len不含49的方案数
dp[len][1] 代表长度为len不含49但是以9开头的数字的方案数
dp[len][2] 代表长度为len含有49的方案数
状态转移如下
dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1]; //如果不含49且开头不为9,在前面可以填上0-9 但是要减去dp[i-1][1] 因为4会和9构成49
dp[i][1] = dp[i-1][0]; //这个直接在不含49的数上填个9就行了
dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1]; //已经含有49的数可以填0-9,或者9开头的填4
写完动态转移方程后就把N从高位到低位一个一个统计了
在统计到某一位的时候,加上dp[i-1][2] * digit[i] 是显然没问题,这是因为这一位可以填【0,(digit[i]-1)】这个区间的数
若这一位之前挨着49,那么加上 dp[i-1][0] * digit[i] 也是显然OK。
若这一位之前没有挨着49,但是digit[i]比4大,那么当这一位填比digit[i]小的4的时候,就得加上dp[i-1][1](以9开头的数字的方案数)
*/
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<map>
#include<cstring>
#include<string>
#include<set>
#include<stack>
#include<queue>
#include<iomanip>
const int MAX=21;
__int64 dp[MAX][3];
int digit[MAX];
using namespace std;
int main()
{
memset(dp,0,sizeof(dp));
dp[0][0]=1; //边界
for(int i=1;i<20;i++)
{
dp[i][0]=dp[i-1][0]*10-dp[i-1][1];
dp[i][1]=dp[i-1][0];
dp[i][2]=dp[i-1][2]*10+dp[i-1][1];
}
int t,len,i,j,p,before;
__int64 sum,n;
cin>>t;
while(t--)
{
cin>>n;
memset(digit,0,sizeof(digit));
len=0;
sum=0;
before=0;
while(n)
{
digit[++len]=n%10; //高位在右边
n/=10;
}
bool p=false;
for(i=len;i>=1;i--)
{
sum += dp[i-1][2] * digit[i];
if(p)
sum += dp[i-1][0] * digit[i];
if(!p && digit[i] >4) //必须要有!p因为dp[i-1][0]的情况包括了dp[i-1][1]
sum += dp[i-1][1];
if(before == 4 && digit[i] == 9)
p = true;
before = digit[i];
}
if(p)
sum++; //本身也算一个
cout<<sum<<endl;
}
return 0;
}
/*统计过程如下:
假设统计N=591时,那么按以下的顺序进行统计:
1~499 确定5这一位,统计的数比它小
500~589 确定9这一位 ,统计的数比它小
590 确定1这一位,统计的数比它小
最后判断一下自身是不是符合 即591
*/
常见转化的技巧,如果给定的区间是[n,m]的话,需要转化为[0,m] - [0,n]的问题形式进行求解。
例题2:
求不含62和4的数目在区间[n,m]内,转化为求[n,m]区间内含有62或者4的问题~
//dp[i][0]:长度为i的不含62和4的个数
//dp[i][1]:长度为i,且最高位含有2的不含62和4的个数
//dp[i][2]:长度为i的含有62或4的个数
void init()
{
memset(dp,0,sizeof(dp));
dp[0][0] = 1;
for(int i=1;i<=8;i++)
{
dp[i][0] = dp[i-1][0]*9(前边不能加4,其他随便) - dp[i-1][1](6与2会组成62);
dp[i][1] = dp[i-1][0];(前边添个2)
dp[i][2] = dp[i-1][0](前边加个4) + dp[i-1][1](前边加个6) + dp[i-1][2] * 10(随便加);
}
}
统计过程:
for(int i=m;i>=1;i--)
{
ans += dp[i-1][2] * A[i];
if(flag)
{
ans += dp[i-1][0] * A[i];
}
else
{
if(A[i]>4) ans += dp[i-1][0]; //填个4
if(A[i+1] == 6 && A[i]>2) ans += dp[i][1];//很重要,上题没有统计是因为9是最大的数字了!
if(A[i]>6) ans += dp[i-1][1];
if(A[i] == 4 || (A[i+1] == 6 && A[i] == 2)) flag = true;
}
}
//数本身
if(flag) ans++;
时刻注意不要重复和遗漏!
数位DP论文: 刘聪 http://wenku.baidu.com/link?url=ST9zKTsTISzDSKh-qWCWj4avfyrwY47AVAZyMZnDyUNRjLAnRKwRiyKOR0rBPBqUryCFbkf2qe6g7u36bIqiCXz-uBm4oq1B_bH-rXJY_Di###
拓展的题目: 这类题目需要经过转化之后才能看出数位dp的思想。
第一题:Amount of degrees (ural 1057)
题目链接:http://acm.timus.ru/problem.aspx?space=1&num=1057
题意:[x,y]范围内的数,可以拆分成k个b进制的不同幂的和 的数字有多少。
我们可以将x转换成二进制来讨论。二进制转化时,找到第一个非0非1的数,将其及其后面的数都变为1.
那么问题就变成了求[0,x]范围内,二进制表示中含有k个1的数字有多少个。
求[x,y]区间相减。我们可以给数建立0,1的表示树。
在求高度为i的完全二叉树中含有j个1的路径有多少个时,递推式为:f[i,j] = f[i-1,j-1] + f[i-1,j]
第二题:HDU 3652 给定区间[1,N]求出含13且是13的倍数的数的数目。
关于整除的问题也是可以利用数位dp的,原因在于a%m == ((b%m)*10 + d)%m ,假如 a == bd的形式的话
实际上用3维就可以,参考资料2中给出的解答是用的4维的数组。
可以设DP[I][J][K]令J为除13后的余数,I为位数,K代表位数中是否出现13
其中k可以为0,1,2分别代表没有出现13,出现13,以及没出现13但是首位为1的情况。
题解:http://www.cnblogs.com/jie-dcai/p/4278404.html
参考资料:
1. 汇总:http://blog.csdn.net/niuox/article/details/9864199
2. http://wenku.baidu.com/link?url=o3ER_gVCyB0qcKthM-Y8vPtAGZ_u5bzOu_gUCUhPcXC6YfaSDgtBSXNEEvvGvSzyuDE9TULcPNsDrRd9IUtQVHeKUVrnPUjyfWjCly_J7Xq 初探数位DP