定义数位dp数组为 f [ x ] [ s t 1 ] [ s t 2 ] . . . [ s t n ] f[x][st1][st2]...[stn] f[x][st1][st2]...[stn]表示第x位状态为 s t 1... s t n st1...stn st1...stn的值。不要把 l i m lim lim放到数组里面,因为当lim = 1时,对于每个x只会进入一次。有时候这样能避免dp数组的每次初始化。
Windy 数
Windy定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为2的正整数被称为Windy数。Windy想知道,在A和B之间,包括A和B,总共有多少个 Windy 数?
#include<bits/stdc++.h>
using namespace std;
int f[12][12],digit[12],cnt;
int dfs(int x,int st,int op){ // x为当前为第几位 st为前缀状态 op表示前缀和当前求解的数字的关系,1为相同 0为小于
if(!x)return 1;
if(!op && ~f[x][st])return f[x][st]; // 记忆化
int maxx=op ? digit[x] : 9;
int res=0;
for(int i=0;i<=maxx;++i){
if(abs(i-st)<2)continue;
if(st==11 && i==0)res+=dfs(x-1,11,op && i==maxx);
else res+=dfs(x-1,i,op && i==maxx);
}
if(!op)f[x][st]=res;
return res;
}
int solve(int n){
cnt=1;
while(n){
digit[cnt++]=n%10;
n/=10;
}
return dfs(cnt-1,11,1)-1; // -1排除0
}
int main(){
memset(f,-1,sizeof f);
int a, b;
while(cin>>a>>b)
cout<<solve(b)-solve(a-1)<<endl;
}
数字计数
求a和b之间的整数,每个数码出现的数量。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
class DP{
public:
ll nums;
vector<ll> v;
DP(ll n=-1):nums(n),v(10) {}
DP operator+ (const DP &x) const{
DP res(nums+x.nums);
for(int i=0;i<10;++i)
res.v[i] = v[i] + x.v[i];
return res;
}
DP operator- (const DP &x) const{
DP res(nums-x.nums);
for(int i=0;i<10;++i)
res.v[i] = v[i] - x.v[i];
return res;
}
};
DP f[15];
int dig[15],cnt;
DP dfs(int x,int st,int op){ // st表示有没有前导零 op表示前缀和要求的数的是否相同
if(!x)return DP(1);
if(!st && !op && ~f[x].nums)return f[x];
int maxx = op ? dig[x] : 9;
DP res(0);
for(int i=0;i<=maxx;++i){
if(st && i==0){ // 排除前导零
res = res + dfs(x-1, 1, op && i==maxx);
}else{
DP tmp = dfs(x-1, 0, op && i==maxx);
res = res + tmp;
res.v[i] += tmp.nums;
}
}
if(!st && !op)f[x] = res;
return res;
}
DP solve(ll x){
cnt = 1;
for(int i=0;i<15;++i)
f[i] = DP(-1);
while(x){
dig[cnt++] = x%10;
x /= 10;
}
return dfs(cnt-1, 1, 1);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
ll a,b;
while(cin>>a>>b){
DP b_ans = solve(b), a_ans = solve(a-1);
DP res = b_ans - a_ans;
for(int i=0;i<10;++i){
cout<<res.v[i]<<' ';
}
cout<<endl;
}
}
同类分布
给出两个数a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
f[x][st][val]
,x为当前所在位,st为前缀和数字和,val为前缀值模mod的值。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=3e5+3;
int mod, dig[20];
ll f[20][170][170];
ll dfs(int x,int st,int val,int lim){
if(!x)return st == mod && val == 0; // 当前缀为mod,值%mod为0时有贡献
if(st>mod)return 0;
if(!lim && ~f[x][st][val])return f[x][st][val];
int maxx = lim ? dig[x] : 9;
ll res=0;
for(int i=0;i<=maxx;++i){
res += dfs(x-1,st+i,(val*10+i)%mod,lim && i==maxx);
}
if(!lim)f[x][st][val] = res;
return res;
}
ll solve(ll x){
ll ans=0;
int cnt=0;
while(x){
dig[++cnt] = x % 10;
x /= 10;
}
for(int i=1;i<162;++i){
memset(f,-1,sizeof f);
mod = i;
ans += dfs(cnt, 0, 0, 1);
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
ll a,b;
cin>>a>>b;
cout<<solve(b)-solve(a-1)<<endl;
}
Valley Numer
当一个数字,从左到右依次看过去数字没有出现先递增接着递减的“山峰”现象,就被称作 Valley Number。它可以递增,也可以递减,还可以先递减再递增。在递增或递减的过程中可以出现相等的情况。比如,1,10,12,212,32122都是 Valley Number。
求不大于N的Valley Number数有多少。
f[x][st][up][ish]
,x为当前位,st为上一个数字的值,up为现在是否为递增,ish为现在是否为最高位。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=1e2+3;
const ll mod=1e9+7;
char dig[maxn];
int f[maxn][10][2][2],cnt=0;
ll dfs(int x,int st,bool up,bool ish,int lim){
if(x==cnt){ return 1;}
if(!lim && ~f[x][st][up][ish])return f[x][st][up][ish];
int maxx = lim ? dig[x]-'0' : 9;
ll res = 0;
for(int i=0;i<=maxx;++i){
if(ish && i==0){
res = (res+dfs(x+1,9,0,1,lim && i==maxx))%mod;
}else if(up){
if(i<st)continue;
res = (res+dfs(x+1,i,1,0,lim && i==maxx))%mod;
}else{
if(i<=st) res = (res+dfs(x+1,i,0,0,lim && i==maxx))%mod;
else res = (res+dfs(x+1,i,1,0,lim && i==maxx))%mod;
}
}
if(!lim)f[x][st][up][ish] = res;
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
cin>>T;
while(T--){
cin>>dig;
cnt = strlen(dig);
memset(f,-1,sizeof f);
cout<<dfs(0,9,0,1,1)-1<<endl;
}
}
当数位dp数组中不放lim时,可以只memset一次。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=1e2+3;
const ll mod=1e9+7;
char dig[maxn];
int f[maxn][10][2][2],cnt=0;
ll dfs(int x,int st,bool up,bool ish,int lim){
if(x==-1)return 1;
if(!lim && ~f[x][st][up][ish])return f[x][st][up][ish];
int maxx = lim ? dig[x]-'0' : 9;
ll res = 0;
for(int i=0;i<=maxx;++i){
if(ish && i==0){
res = (res+dfs(x-1,9,0,1,lim && i==maxx))%mod;
}else if(up){
if(i<st)continue;
res = (res+dfs(x-1,i,1,0,lim && i==maxx))%mod;
}else{
if(i<=st) res = (res+dfs(x-1,i,0,0,lim && i==maxx))%mod;
else res = (res+dfs(x-1,i,1,0,lim && i==maxx))%mod;
}
}
if(!lim)f[x][st][up][ish] = res;
return res;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
int T;
cin>>T;
memset(f,-1,sizeof f);
while(T--){
cin>>dig;
cnt = strlen(dig);
reverse(dig,dig+cnt); // 保持f数组下标0为个位
cout<<dfs(cnt-1,9,0,1,1)-1<<endl;
}
}
Beautiful numbers
求
9
e
18
9e18
9e18范围内,[a,b]之间的数中符合<每个非零数位都能整除这个数>的数的数量。思路:问题可转化为每个非零数位的lcm能整除这个数。由于2520是1-9的lcm,问题转化为每个非零数位的lcm能整除这个数取模2520.
f[x][st][op]
,x为当前位,st为前缀lcm,op为当前的前缀取模2520.
特殊:由于f数组和lim无关,所以在不同次查询中都能用!只要初始化一次! 否则会超时。
#include<bits/stdc++.h>
using namespace std;
//#define endl '\n'
using ll = long long;
using pii = pair<int,int>;
const int maxn=1e2+3;
const ll mod = 2520;
int arr[][4]={{1,2,4,8},{1,3,9},{1,5},{1,7}}, na[]={4,3,2,2};
ll f[20][50][2523];
int dig[20],ma[2523];
vector<int> num;
int gcd(int a,int b){
return a%b ? gcd(b,a%b) : b;
}
void init(int x,int now){
if(x&4){num.push_back(now);return;}
for(int i=0;i<na[x];++i)
init(x+1,arr[x][i]*now);
}
ll dfs(int x,int st,int op,int lim){
if(!x)return op%st==0;
if(!lim && ~f[x][ma[st]][op])return f[x][ma[st]][op];
int maxx = lim ? dig[x] : 9;
ll ans=0;
for(int i=0;i<=maxx;++i){
if(i==0 || st % i == 0)
ans += dfs(x-1,st,(op*10+i)%mod,lim&&i==maxx);
else
ans += dfs(x-1,st*i/gcd(st,i),(op*10+i)%mod,lim&&i==maxx);
}
if(!lim)f[x][ma[st]][op]=ans;
return ans;
}
ll solve(ll x){
int cnt=0;
while(x){
dig[++cnt] = x % 10;
x /= 10;
}
return dfs(cnt, 1, 0, 1);
}
int main(){
memset(f,-1,sizeof f);
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
init(0,1);
for(int i=0;i<num.size();++i)
ma[num[i]] = i;
int T;
cin>>T;
while(T--){
ll a,b;
cin>>a>>b;
cout<<solve(b)-solve(a-1)<<endl;
}
}
P3286 [SCOI2014]方伯伯的商场之旅
题意:对于一个数
i
i
i,把它看成
k
k
k进制数,把所有位上的数移动到一个位上,代价是移动的石子数量 × 移动的距离。问把
[
L
,
R
]
[L,R]
[L,R]内每个数都进行一次合并,求最小合并代价。
思路:先假设所有数都合并到个位,很容易能得到总代价。然后考虑移动。当一个n位数的
s
u
m
[
n
.
.
.
i
]
>
s
u
m
[
i
−
1...1
]
sum[n...i]>sum[i-1...1]
sum[n...i]>sum[i−1...1] 时,它合并到的点必然
≥
i
≥i
≥i,所以并且代价应该减少
s
u
m
[
n
.
.
.
i
]
−
s
u
m
[
i
−
1...1
]
sum[n...i]-sum[i-1...1]
sum[n...i]−sum[i−1...1]。所以可以贪心一位一位进行数位DP。
#include<bits/stdc++.h>
using namespace std;
//#define endl '\n'
#define IOS ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
typedef long long LL;
const int N = 5e4 + 5, K = 105;
const int inf = 1e9;
const long long mod = 1e9 + 7;
using LD = long double;
using LL = long long;
using PII = pair<int, int>;
/*
* long long !!!
* array size !!!
*/
int dig[1005];
LL f[105][1505], pos, k;
LL dfs(int x, int st, int lim){
if(pos > 1 && st < 0)return 0;
if(!x)return st;
if(!lim && ~f[x][st])return f[x][st];
int maxx = lim ? dig[x] : k - 1;
LL res = 0;
for(int i = 0; i <= maxx; ++i){
if(pos == 1)res += dfs(x-1, st + i * (x - 1), lim && i == dig[x]);
else if(x >= pos)res += dfs(x-1, st + i, lim && i == dig[x]);
else res += dfs(x-1, st - i, lim && i == dig[x]);
}
if(!lim)f[x][st] = res;
return res;
}
LL solve(LL n){
int cnt = 0;
while(n){
dig[++cnt] = n % k;
n /= k;
}
pos = 1;
memset(f, -1, sizeof(f));
LL ans = dfs(cnt, 0, 1);
for(pos = 2; pos <= cnt; ++pos){
memset(f, -1, sizeof(f));
//cout << ans << endl;
ans -= dfs(cnt, 0, 1);
}
return ans;
}
int main(){
IOS;
LL l, r;
cin >> l >> r >> k;
cout << solve(r) - solve(l - 1) << endl;
}