数位DP
一般求区间内满足限制的数的个数,状态设计考虑几位数和大小关系。
条件一般与数的大小无关,而与数的组成有关。
善于用不同的进制处理。
数位DP外套网络流、贪心、枚举。
一般的数据范围是1 e 9到1 e 18。
填数的过程中要满足上限限制和题目其他限制。
一般用记忆化搜索求答案。
HDU3652
统计区间 [1,n] 中含有 '13' 且模 13 为 0 的数字有多少个。
N<=10^9
分析
设f[i][j][0/1][0/1]表示位数为i,数位之和为j,是否含有数字13,最后一位是否是1的数字的个数。
f不直接求,而是记忆化搜索,一边试填一边求f。
输入数n,设n的位数是m,我们想构造一个小于n且满足条件的数字。问题转化为诸位填数。
假如n = 13245,现在前3位填的是132,那么为了得到的数小于n,第4位不能大于4。
如果现在前3位填的是非132的任意合法的数(不能是999这样的),那么第4位可以填0~9中任意一个。
如果前面填的数中出现了13,后面无论填什么都是满足这个条件的。
如果前面填的数中没有出现13,但是上一位数字是1,那么当前位置填3的话,就有13了。
如果前面填的数的数位之和模13是sum,我们枚举当前位置所填的数字i,当前数字的数位之和模13就是(sum + i * ten[pos]) mod 10,其中pos是从低到高第pos位,ten[pos]表示10的幂次,需要预处理。
代码
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
long long read()
{
long long x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int n, m, num[20];
int ten[20], f[20][13/*数字和*/][2/*前面是否出现过13*/][2/*最后一位是否是1*/];//[2/*是否达到上限*/];
int dfs(int pos, int sum, bool hav13, bool limit, int last){
if(pos == 0 && sum == 0 && hav13){
return 1;
}
if(pos == 0){
return 0;
}
if(!limit && f[pos][sum][hav13][last == 1] != -1){
return f[pos][sum][hav13][last == 1];
}
int res = 0, up;
if(limit == 1) up = num[pos];
else up = 9;
for(int i = 0; i <= up; ++i){
res += dfs(pos - 1, (sum + i * ten[pos]) % 13, hav13 || (last == 1 && i == 3), limit && (i == up/*i == num[pos]*/), i);
}
if(!limit) f[pos][sum][hav13][last == 1] = res;
return res;
}
int main()
{
memset(f, -1, sizeof(f));
ten[1] = 1;
for(int i = 2; i <= 13; ++i) ten[i] = ten[i - 1] * 10;
while(scanf("%d", &n) != EOF ){
m = 0;
while(n){
num[++m] = n % 10;
n /= 10;
}
printf("%d
", dfs(m , 0, 0, 1, 0));
}
return 0;
}
关于数位dp的经验
-
注意很多时候带进去是n==0要特殊处理。 ◦
-
还有一般问[m,n],我们求[1,n]-[1,m-1]但是有的时候m为0就炸了。然 后一道题wa一个小时。。。。。。正常。。。。。
-
求所有包含49的数,其实就是(总数-所有不包含49的数)。前者的化需要有两维限制,一个是上一位是什么,一个是之前有没有49。但是后者只需要记一个上一位是什么。就能好写一些。
-
一般问题的数位dp部分,都是套路,但是这并不代表它外面“华丽的 外衣”和与其他算法结合的的部分也是无脑的。要看出它是考数位dp, 要看出问题怎么变化一下就是数位dp了。
-
dp初始化memset要置为-1。不能置为0!!!!!!因为有很多时候 dp值就应该是0,然后我们如果误以为是因为之前没有计算,从新计算的话,就会tle。这里不能写成0。
-
既然是记忆化搜索,那就可以剪枝!!!!可行性剪枝!!
-
有时前导0也需要记的!!!