数位dp入门
hdu 3555
题意: 求1到n中有多少个数字包含了串49
解法一:
长度 L 的数字中不包含 49 且最高位不是 9 的个数 dp[L][0]
长度 L 的数字中不包含 49 串中最高位是 9 的个数 dp[L][1]
长度 L 的数字中包含 49 串的个数 dp[L][2]
预处理数位
初始化dp[0][0] = 1
dp[L][0] = 9 * dp[L-1][0] + 8 * dp[L-1][1]
dp[L][1] = dp[L-1][1] + dp[L-1][0]
dp[L][2] = 10 * dp[L-1][2] + dp[L-1][1]
然后逐位统计答案即可
LL dp[20][3];
int digit[20];
void init(){
dp[0][0] = 1;
for(int i = 1;i <= 18;i++){
dp[i][0] = 9 * dp[i-1][0] + 8 * dp[i-1][1];
dp[i][1] = dp[i-1][1] + dp[i-1][0];
dp[i][2] = 10 * dp[i-1][2] + dp[i-1][1];
}
}
LL cal(LL n){///注意这种做法是0到n-1的 所以n++
n++;
int pos = 0;
while(n){
digit[++pos] = n % 10;
n /= 10;
}
LL ans = 0;
int last = 0;
bool flag = false;
for(int i = pos;i;i--){
for(int j = 0;j < digit[i];j++){
ans += dp[i-1][2];
if(flag) ans += dp[i-1][0] + dp[i-1][1];///前缀已经包含了49
else if(j == 4) ans += dp[i-1][1];///当前位为4
}
if(last == 4 && digit[i] == 9) flag = true;
last = digit[i];
}
return ans;
}
int main(){
init();
int T;
for(scanf("%d",&T); T; T--){
LL n;
scanf("%lld",&n);
printf("%lld\n",cal(n));
}
return 0;
}
- 但是这种做法很麻烦,在计算答案的时候需要分类讨论,要是情况比较复杂就爆炸了
通用解法 dfs(pos,state,bounded)
/*求出0到n有多少个包含49的数字
将数字分为两段,前缀和后缀
state = 0 前缀不包含49 最后一个数字不为4
state = 1 前缀不包含49 最后一个数字为4
state = 2 前缀包含49
dp[pos][state] 则表示在前缀状态为state的情况下,后缀长度为pos,前后缀拼起来包含49的数字的个数*/
LL dp[20][3];
int digit[20];
int next_state(int cur_state,int in){
if(cur_state == 0 && in == 4)
cur_state = 1;
else if(cur_state == 1 && in == 9)
cur_state = 2;
else if(cur_state == 1 && in != 4)
cur_state = 0;
return cur_state;
}
LL dfs(int pos,int state,bool bounded){
if(pos == 0)
return state == 2;
if(!bounded && dp[pos][state]!=-1)
return dp[pos][state];
LL ans = 0;
int ed = bounded?digit[pos]:9;
for(int i = 0;i <= ed;i++)
ans += dfs(pos - 1, next_state(state,i), bounded && i == ed);
if(!bounded){ ///不取上界时,可以取满
dp[pos][state] = ans;
}
return ans;
}
LL cal(LL x){
int pos = 0;
while(x){
digit[++pos]= x % 10;
x /= 10;
}
return dfs(pos, 0, true);
}
int main()
{
memset(dp, -1, sizeof(dp));
int T;
for(scanf("%d",&T); T; T--){
LL n;
scanf("%lld",&n);
printf("%lld\n",cal(n));
}
return 0;
}
hdu 3652
求[1,n]内有多少个数字,该数字有13,且能被13整除 n<=10^9
上一题的加强版 需要修改的就是state的状态定义
分成两个子状态
remainder 前缀对13取余
have 前缀不包含13 前缀不包含13且最后一位数字是1 前缀包含13 0 1 2表示
int dp[20][13][3];
int digit[20];
int next_have(int cur_have,int in){
if(cur_have == 0 && in == 1)
cur_have = 1;
else if(cur_have == 1 && in == 3)
cur_have = 2;
else if(cur_have == 1 && in != 1)
cur_have = 0;
return cur_have;
}
int dfs(int pos, int remainder, int have, bool bounded){
if(pos == 0)
return have == 2 && remainder == 0;
if(!bounded && dp[pos][remainder][have]!=-1)
return dp[pos][remainder][have];
int ans = 0;
int ed = bounded?digit[pos]:9;
for(int i = 0;i <= ed;i++)
ans += dfs(pos - 1, (remainder * 10 + i)%13, next_have(have,i), bounded && i == ed);
if(!bounded){ ///不取上界时,可以取满
dp[pos][remainder][have] = ans;
}
return ans;
}
int cal(int x){
int pos = 0;
while(x){
digit[++pos]= x % 10;
x /= 10;
}
return dfs(pos, 0, 0, true);
}
int main()
{
memset(dp, -1, sizeof(dp));
int n;
while(scanf("%d",&n)==1){
printf("%d\n",cal(n));
}
return 0;
}
hdu 4352
\(题意: 求出L,R之间有多少个数字 满足 其最长严格递增子序列的长度为K(1<=K<=10), 0<L<=R<=2^{63}-1\)
思路:按照最长上升子序列nlogn的解法,如果子序列的长度相同,那么最末尾的元素较小的有望
形成更长的子序列,可以用dp[i]表示长度为i的最长上升子序列的末尾元素的最小值,每次二分去做替换。
由于这里K很小,所以可以状压表示这个dp数组,于是这道题的状态定义就是dp[pos][state][K]
前缀能够构成的上升子序列状态为state,后缀pos位,组合起来拼成的最长上升子序列长度为K的答案,直接枚举位置更新状态即可
LL dp[20][1<<10][11];
int cnt[1<<10];
int digit[20];
int K;
int cur_state(int state,int in){
for(int i = in;i < 10;i++){
if(state & (1<<i)){
state ^= (1<<i);
break;
}
}
state |= (1<<in);
return state;
}
LL dfs(int pos, int state, bool leading_zero, bool bounded){
if(pos == 0) return cnt[state] == K;
if(!leading_zero && !bounded && dp[pos][state][K]!=-1)
return dp[pos][state][K];
LL ans = 0;
int ed = bounded?digit[pos]:9;
for(int i = 0;i <= ed;i++){
if(leading_zero){
if(i == 0) ans += dfs(pos - 1, state, true, bounded && i == ed);
else ans += dfs(pos - 1, cur_state(state, i), false, bounded && i == ed);
}else{
ans += dfs(pos - 1,cur_state(state,i),false,bounded && i == ed);
}
}
if(!leading_zero && !bounded){ ///不取上界时,可以取满
dp[pos][state][K] = ans;
}
return ans;
}
LL cal(LL x){
int pos = 0;
while(x){
digit[++pos]= x % 10;
x /= 10;
}
return dfs(pos, 0, true, true);
}
int main()
{
for(int s = 0;s < (1<<10);s++){
cnt[s] = 0;
for(int i = 0;i <10;i++) if(s & (1<<i)) cnt[s]++;
}
memset(dp, -1, sizeof(dp));
int T, cas = 1;
LL L, R;
cin>>T;
while(T--){
scanf("%lld%lld%d",&L,&R,&K);
printf("Case #%d: %lld\n",cas++,cal(R) - cal(L-1));
}
return 0;
}
/*
5
123 345 2
12 123411 5
12 123411 2
12 123411 3
12 123411 4
*/
SPOJ Balanced numbers
题意: 定义balanced numbers为每个数出现的数字中 每个奇数出现的次数为偶数次,偶数出现的次数为奇数次,
求区间L,R有多少个这样的数字
思路: 一开始直接用状压 0,1 表示每个数字出现的次数的奇偶,敲完后发现样例不对,想了想发现我这样不能判断偶数是否出现过,所以要用三进制来状压
LL dp[20][60000]; /// 3^10 = 59049 0代表没出现 1代表出现过奇数次 2代表出现过为偶数次
int digit[20];
int base[10];
int cur_state(int state,int in){
int tmp = state;
for(int i = 0;i < in;i++) tmp /= 3;
if(tmp % 3 != 2) state += base[in];
else state -= base[in];
return state;
}
LL dfs(int pos, int state, int leading_zero, bool bounded){
if(pos == 0) {
for(int i = 0;i < 10;i++){
if(state % 3 == 1 && i % 2) return 0;
if(state % 3 == 2 && !(i % 2)) return 0;
state /= 3;
}
return 1;
}
if(!leading_zero && !bounded && dp[pos][state]!=-1)
return dp[pos][state];
LL ans = 0;
int ed = bounded?digit[pos]:9;
for(int i = 0;i <= ed;i++){
if(leading_zero){
if(i == 0) ans += dfs(pos - 1, state ,true, bounded && i == ed);
else ans += dfs(pos - 1,cur_state(state, i), false, bounded && i == ed);
}else ans += dfs(pos - 1,cur_state(state, i), false, bounded && i == ed);
}
if(!leading_zero && !bounded){ ///不取上界时,可以取满
dp[pos][state] = ans;
}
return ans;
}
LL cal(ULL x){
int pos = 0;
while(x){
digit[++pos] = x % 10;
x /= 10;
}
return dfs(pos, 0, true, true);
}
int main()
{
base[0] = 1;
for(int i = 1;i < 10;i++) base[i] = base[i-1] * 3;
memset(dp, -1, sizeof(dp));
int T;
ULL L, R;
cin>>T;
while(T--){
scanf("%llu%llu",&L,&R);
printf("%llu\n",cal(R) - cal(L-1));
}
return 0;
}
/*
*/
codeforces 55D Beautiful numbers
\(题意:求区间[L,R]中有多少数字能整除本身所有非零位的数字 1<=L<=R<=9*10^{18}\)
题解:
- 一个数能整除一些数,即这个数能整除这些数的lcm
- X % a = X % (a * b) % a
有什么用么?
我们需要保存前缀的有关状态,当然不可能直接把数字存下来,所以要缩小范围但是要不影响其作用
很容易想到的就是取余了
MOD = lcm(1,2,....9) = 2520
假设数为X,其所有非零位数字的lcm为lcmx
MOD % lcmx = 0 是显然的
于是X % lcmx = X % MOD % lcm,这样就把X的范围缩小了
这样就很清楚了,保存前缀的两个状态
- 1 对MOD 取余
- 2 数字的lcm
但是dp[20][2520][2520]爆内存了,打表会发现1到9的lcm组合只有48种,离散化处理一下就好了
#include<bits/stdc++.h>
#define LL long long
#define ULL unsigned long long
#define P pair<int,int>
using namespace std;
int id[2521];
LL dp[20][2521][49];///pos, remainder, lcm
int digit[20];
int Gcd(int x,int y){
return y == 0?x:Gcd(y,x%y);
}
int Lcm(int x,int y){
return x / Gcd(x, y) * y;
}
void init(){
int ix = 0;
for(int s = 2;s < (1<<10);s++){
int tmp = 1;
for(int i = 1;i < 10;i++){
if(s & (1<<i)) tmp = Lcm(tmp,i);
}
if(!id[tmp]) id[tmp] = ++ix;
}
memset(dp, -1, sizeof(dp));
}
LL dfs(int pos, int remainder, int lcm, bool leading_zero, bool bounded){
if(pos == 0) {
return remainder % lcm == 0;
}
if(!leading_zero && !bounded && dp[pos][remainder][id[lcm]]!=-1)
return dp[pos][remainder][id[lcm]];
LL ans = 0;
int ed = bounded?digit[pos]:9;
for(int i = 0;i <= ed;i++){
if(i) ans += dfs(pos - 1, (remainder * 10 + i)%2520, Lcm(lcm,i), false, bounded && i == ed);
else if(leading_zero) ans += dfs(pos - 1, remainder, lcm, true, bounded && i == ed);
else ans += dfs(pos - 1, remainder * 10 % 2520, lcm, false, bounded && i == ed);
}
if(!leading_zero && !bounded){ ///不取上界时,可以取满
dp[pos][remainder][id[lcm]] = ans;
}
return ans;
}
LL cal(LL x){
int pos = 0;
while(x){
digit[++pos] = x % 10;
x /= 10;
}
return dfs(pos, 0, 1, true, true);
}
int main()
{
init();
int T;
cin>>T;
while(T--){
LL L, R;
cin>>L>>R;
cout<<cal(R) - cal(L - 1)<<endl;
}
return 0;
}
/*
*/