数位dp的一些基础元素:
pos:位数,lead:前导零判断,limit:数位上界变量
pre:前一个数,state:当前状态(一般与pre重合)
两个函数:dfs(递归,dp)+solve(数拆分)
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
对比
2^30-->1e9 int-->2e9
2^60-->1e18 ll -->9e18
一般开35就够了,dp用int
入门题:HDU 2089 不要62
无前导零考虑,基础思考方式。
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e3+7;
int n,m,cnt;
int a[30],dp[30][2];
int dfs(int pos,int pre,int x,bool limit){
if(pos==-1) return 1;
if(!limit&&dp[pos][x]!=-1) return dp[pos][x];
int mx=limit?a[pos]:9;
int ans=0;
for(int i=0;i<=mx;i++){
if(i==4) continue;
if(pre==6&&i==2) continue;
ans+=dfs(pos-1,i,i==6,limit&&(i==a[pos]));
}
if(!limit) dp[pos][x]=ans;
return ans;
}
int solve(int x){
int pos=0;
while(x){
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,0,1);
}
int main(){
memset(dp,-1,sizeof(dp));
while(cin>>n>>m&&n+m){
printf("%d\n",solve(m)-solve(n-1));
}
return 0;
}
简单题:
HYSBZ 1026 windy数
注意前导零操作
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e3+7;
ll n,m,cnt;
ll a[30],dp[30][10];
ll dfs(int pos,int pre,bool lead,bool limit){
if(pos==-1) return 1;
if(!limit&&dp[pos][pre]!=-1&&pre>0) return dp[pos][pre];
//pre>0,如果pre==0,涉及到位数,需要再次循环
int mx=limit?a[pos]:9;
ll ans=0;
for(int i=0;i<=mx;i++){
if(abs(i-pre)>=2||lead)
ans+=dfs(pos-1,i,lead&&(i==0),limit&&(i==a[pos]));
}
if(!limit) dp[pos][pre]=ans;
return ans;
}
int solve(int x){
int pos=0;
while(x){
a[pos++]=x%10;
x/=10;
}
return dfs(pos-1,-1,1,1);
}
int main(){
memset(dp,-1,sizeof(dp));
while(cin>>n>>m&&n+m){
printf("%lld\n",solve(m)-solve(n-1));
}
return 0;
}
还有普通dp预处理,有前导零时,可能出漏情况的错误,还是少写吧。
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
const int maxn=1e3+7;
ll n,m,cnt;
ll a[14],dp[14][10];
void init(){
for(int i=0;i<=9;i++) dp[1][i]=1;
for(int i=2;i<14;i++)
for(int j=0;j<=9;j++)
for(int k=0;k<=9;k++)
if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];
}
int solve(int x){
cnt=0; int ans=0;
while(x){
a[++cnt]=x%10;
x/=10;
}
a[cnt+1]=0;
for(int i=1;i<cnt;i++) for(int j=1;j<=9;j++) ans+=dp[i][j];
for(int i=1;i<a[cnt];i++) ans+=dp[cnt][i];
for(int i=cnt-1;i>0;i--){
for(int j=a[i]-1;j>=0;j--)
if(abs(a[i+1]-j)>=2) ans+=dp[i][j];
if(abs(a[i+1]-a[i])<2) break;
}
return ans;
}
int main(){
init();
while(cin>>n>>m) printf("%d\n",solve(m+1)-solve(n));
}
POJ - 3252 Round Numbers
规定一个数如果二进制中0的个数大于等于1的个数,则这个数称为“整数”,问闭区间a,b中有多少个整数。
题目分析:数位dp,dp[pos][c0][c1]代表前pos为中有c0个0和c1个1的整数数量
1、三维dp,范围最大ll也就70,不慌
2、limit的判断,可以用万能的 limit&&i==a[pos]
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
int dp[50][50][50],a[35],n,m,cnt;
int dfs(int pos,int c0,int c1,bool lead,bool limit){
if(pos==-1){
if(c0>=c1) return 1;
return 0;
}
if(!limit&&!lead&&dp[pos][c0][c1]!=-1) return dp[pos][c0][c1];
int mx=limit?a[pos]:1;
int ans=0;
for(int i=0;i<=mx;i++){
if(lead&&i==0) ans+=dfs(pos-1,0,0,1,limit&&i==a[pos]);
else if(i) ans+=dfs(pos-1,c0,c1+1,0,limit&&i==a[pos]);
else ans+=dfs(pos-1,c0+1,c1,0,limit&&i==a[pos]);
}
if(!limit&&!lead&&dp[pos][c0][c1]==-1) dp[pos][c0][c1]=ans;
return ans;
}
int solve(int x){
cnt=0;
while(x){
a[cnt++]=x&1;
x>>=1;
}
return dfs(cnt-1,0,0,1,1);
}
int main(){
memset(dp,-1,sizeof(dp));
while(cin>>n>>m) printf("%d\n",solve(m)-solve(n-1));
}
这里附上一篇用dp预处理的博客,有前导零时,solve函数写得还是比较复杂的,高判立下。但也可以借鉴。
中等题
HDU-3709 Balanced Number
题意:如果某个数可以从中选择一位当作对称点,然后左右两边的数字乘以该数字到对称点的距离的和相等,那么该数就被成为平衡数,比如4139,以3为对称点,4 * 2 + 1 * 1=9 * 1。所以该数是平衡数。现在给你一个区间,问该区间内有多少个平衡数。
1、三维,数位dp已经很常见了,用于问题拆分。
2、设变量有很多种,但得清晰
#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
ll dp[25][25][500],a[25],T,n,m,cnt;
//sum最大500>18*9
ll dfs(int pos,int mid,int sum,bool limit){
if(pos==-1) return sum==0;
if(sum<0) return 0;
if(!limit&&dp[pos][mid][sum]!=-1) return dp[pos][mid][sum];
int mx=limit?a[pos]:9;
ll ans=0;
for(int i=0;i<=mx;i++) ans+=dfs(pos-1,mid,sum+(pos-mid)*i,limit&&i==a[pos]);
if(!limit) dp[pos][mid][sum]=ans;
return ans;
}
ll solve(ll x){
cnt=0;
while(x){
a[cnt++]=x%10;
x/=10;
}
ll ans=0;
for(int i=0;i<cnt;i++) ans+=dfs(cnt-1,i,0,1);
return ans-cnt+1;
//减去 0000 ,000 ,00这类多个0,但0是算的
}
int main(){
cin>>T;
memset(dp,-1,sizeof(dp));
while(T--){
cin>>n>>m;
printf("%lld\n",solve(m)-solve(n-1));
}
}
/*
2
0 9
7604 24324
10
897
*/