数位 d p dp dp习题

P2657 [SCOI2009] windy 数

思路:数位 d p dp dp

常用模板:四个参数 x , p r e , l e a d , l i x,pre,lead,li x,pre,lead,li

x : x: x: 当前遍历到第几位(从高位开始)。

p r e : pre: pre: 当前位的前一位是什么,不同题目的 p r e pre pre可能不同。

l e a d : lead: lead: 当前位前面是否有前导0.

l i : li: li: 是否为最高位。

记忆化搜索。

d p [ i ] [ j ] dp[i][j] dp[i][j]表示位数为 i i i且前一个数为 j j j的答案,不同题目的第二维可能不同,本题因为要求相邻数大于等于2,所以前一位不同答案也不同。

时间复杂度: O O O( 位数 × \times × 每位可选的数 ) ) )

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
int a[15],l,r,dp[15][10];
int dfs(int x,int pre,bool lead,bool li){
	int ans=0,mx;
	if(!x) return 1;
	if(!li&&!lead&&~dp[x][pre]) return dp[x][pre];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++){
		if(lead||abs(i-pre)>=2){
			ans+=dfs(x-1,i,lead&(!i),li&(i==mx));
		}
	}
	if(!li&&!lead) dp[x][pre]=ans;
	return ans;
} 
int fun(int n){
	int w=0;while(n){
		a[++w]=n%10;n/=10;
	}return dfs(w,0,1,1);
}
int main(){
	scanf("%d%d",&l,&r);mst(dp,-1);
	printf("%d\n",fun(r)-fun(l-1));
	return 0;
}

P6218 [USACO06NOV] Round Numbers S

题意:求从 [ l , r ] [l,r] [l,r]中二进制 c n t 0 ≥ c n t 1 cnt_0\ge cnt_1 cnt0cnt1的数的个数。

思路:数位 d p dp dp裸题,注意前导0.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=65,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
int l,r,a[N];
int dp[N][N];
int dfs(int x,int d,bool lead,bool li){
	int ans=0,mx;
	if(!x) return d>=30;
	if(!lead&&!li&&~dp[x][d]) return dp[x][d];
	mx=li?a[x]:1;
	for(int i=0;i<=mx;i++)
		ans+=dfs(x-1,d+(!i?(lead?0:1):-1),lead&(!i),li&(i==mx)); 
	if(!lead&&!li)  dp[x][d]=ans;
	return ans;
}
int fun(int n){
	int w=0;while(n){
		a[++w]=n%2,n/=2;
	}return dfs(w,30,1,1);
}
int main(){
	scanf("%d%d",&l,&r); mst(dp,-1);
	printf("%d\n",fun(r)-fun(l-1));
	return 0;
}

P2602 [ZJOI2010]数字计数

题意:求 [ l , r ] [l,r] [l,r] 0 , 1 , 2 , 3 , … , 9 0,1,2,3,\dots,9 0,1,2,3,,9出现的次数。

d p [ i ] [ j ] dp[i][j] dp[i][j]表示对于 i i i位数前面有 j j j n u m num num的答案,这样

设置状态就可以保证 d p dp dp的唯一性。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=65,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
ll l,r;
int a[N],num;
ll dp[N][N];
ll dfs(int x,bool lead,bool li,ll s){
	//printf("x=%d,pre=%d\n",x,pre);
	ll ans=0,mx;
	if(!x) return s;
	if(!lead&&!li&&~dp[x][s]) return dp[x][s];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++)
		ans+=dfs(x-1,lead&(!i),li&(i==mx),s+((i||!lead)&&(i==num))); 
	if(!lead&&!li)  dp[x][s]=ans;
	return ans;
}
ll fun(ll n){
	mst(dp,-1);
	int w=0;while(n){
		a[++w]=n%10,n/=10;
	}return dfs(w,1,1,0);
}
int main(){
	scanf("%lld%lld",&l,&r); 
	printf("%lld",fun(r)-fun(l-1));
	for(++num;num<10;++num){
		printf(" %lld",fun(r)-fun(l-1));
	}printf("\n");
	return 0;
}

P3413 SAC#1 - 萌数

题意:求 [ l , r ] [l,r] [l,r]的有包含长度大于 1 1 1的回文串的数的个数。

思路:
显然我们只需要判断一个数存不存在 a a , a b a aa,aba aa,aba这两种情况即可。
d f s dfs dfs时需要维护一个 p r e pre pre p p r e ppre ppre
然后 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示位数 i i i 前一个数是 p r e pre pre 是否为回文的数的个数。

char l[N],r[N];
int a[N];
ll dp[N][10][2];
ll dfs(int x,int pre,int ppre,bool hw,bool o,bool li){
	ll ans=0,mx;
	if(!x) return hw;
	if(!li&&~dp[x][pre][hw]) return dp[x][pre][hw];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++)
		ans=(ans+dfs(x-1,i,!o?pre:-1,hw||(!o&&(i==pre||i==ppre)),o&(!i),li&(i==mx)))%mod;
	if(!li&&!o&&~ppre) return dp[x][pre][hw]=ans%mod;
	return ans%mod;
}
ll fun(char *s){
	mst(dp,-1);
	int w=0;
	int st=1,n=strlen(s+1);
	while(st<n&&s[st]=='0') st++;
	while(st<=n) a[++w]=s[n--]-'0';
	return dfs(w,-1,-1,0,1,1);
}
int main(){
	scanf("%s%s",l+1,r+1);
	int n=strlen(l+1);
	while(n>1&&l[n]=='0') l[n--]='9';
	l[n]-=1;
	printf("%lld\n",(fun(r)-fun(l)+mod)%mod);

P4317 花神的数论题

水题,求1的个数为 2 , … , x 2,\dots,x 2,,x的数的个数之后,然后快速幂求积即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=75,M=2e4+5,inf=0x3f3f3f3f,mod=1e7+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
ll n;
int a[N];
ll ksm(ll a,ll n){
	ll ans=1;
	while(n){
		if(n&1) ans=ans*a%mod;
		a=a*a%mod;
		n>>=1;
	}return ans;
}
ll dp[N][N];
int num;
ll dfs(int x,bool li,int s){
	ll ans=0,mx;
	if(!x) return s==num;
	if(!li&&~dp[x][s]) return dp[x][s];
	mx=li?a[x]:1;
	for(int i=0;i<=mx;i++)
		ans+=dfs(x-1,li&(i==mx),s+(i==1));
	if(!li) return dp[x][s]=ans;
	return ans;
}
ll fun(ll n){
	int w=0;
	ll ans=1,m=n;
	while(n) a[++w]=n%2,n/=2;
	for(int i=2;i<=(int)log2(m)+1;i++){
		num=i;
		mst(dp,-1);
		ll x=dfs(w,1,0);
		if(x) ans=ans*ksm(1LL*i,x)%mod;
	}return ans;
}
int main(){
	scanf("%lld",&n);
	printf("%lld\n",fun(n));
	return 0;
}

2020ICPC上海 C.Sum of Log

题意:求 ∑ i = 0 x ∑ j = [ i = = 0 ] y ( i & j = 0 ) × [ l o g ( i + j ) + 1 ] \sum\limits_{i=0}^x\sum\limits_{j=[i==0]}^y(i\&j=0)\times[log(i+j)+1] i=0xj=[i==0]y(i&j=0)×[log(i+j)+1]

转化一下题意就是,求所有数对满足与为0,的最高位1的位数和。
显然是数位 d p dp dp
考虑状态 d p [ x ] [ i ] [ j ] [ k ] dp[x][i][j][k] dp[x][i][j][k]表示当前位两个数是否位最高位且此时位是否位最高位 1 1 1的答案。

此题貌似开二维会超时,所以时间换空间,开四维直接记忆化搜索即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
int T;
int a[N],b[N];
ll dp[N][2][2][2];
ll res;
ll dfs(int x,bool l1,bool l2,bool f){
	ll ans=0,up1,up2;
	if(!x) return 1;
	if(~dp[x][l1][l2][f]) return dp[x][l1][l2][f];
	up1=l1?a[x]:1,up2=l2?b[x]:1;
	for(int i=0;i<=up1;i++)
		for(int j=0;j<=up2;j++){
			if(i&j) continue;
			ll tmp=dfs(x-1,l1&(i==up1),l2&(j==up2),f|i|j);
			ans=(ans+tmp)%mod;
			if(!f&&(i|j)) res=(res+tmp*x)%mod;
		}
	 return dp[x][l1][l2][f]=ans;
}
ll fun(int x,int y){
	if(x>y) swap(x,y);
	int w=0;
	res=0,mst(dp,-1);
	while(y) b[++w]=y%2,y/=2;
	for(int i=1;i<=w;i++) a[i]=x%2,x/=2;
	ll xxx=dfs(w,1,1,0);
	return res;
}
int main(){
	scanf("%d",&T);
	while(T--){
		int x,y;scanf("%d%d",&x,&y);
		printf("%lld\n",fun(x,y));
	}
	return 0;
}

P4999 烦人的数学作业

题意 : [ l , r ] [l,r] [l,r]所有数位上的数之和。

思路:令 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 表示当前 i i i位和为 j j j 是否为最高位的和。然后跑数位 d p dp dp即可。


int T;
int a[N];
ll l,r,dp[70][N][2];
ll dfs(int x,int s,bool li){
	ll ans=0,mx;
	if(!x) return s;
	if(~dp[x][s][li]) return dp[x][s][li];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++)
		ans=(ans+dfs(x-1,s+i,li&(i==mx)))%mod;
	return dp[x][s][li]=ans%mod;
}
ll fun(ll n){
	mst(dp,-1);
	int w=0;
	while(n) a[++w]=n%10,n/=10;
	return dfs(w,0,1);
}
int main(){
	scanf("%d",&T);
	while(T--){
		//mst(dp,-1);
		scanf("%lld%lld",&l,&r);
		printf("%lld\n",(fun(r)-fun(l-1)+mod)%mod);
	}
	return 0;
}

SP10606 BALNUM - Balanced Numbers

题意:求 [ l , r ] [l,r] [l,r]每个数的数位偶数出现奇数次,奇数出现偶数的数的个数和。

考虑用状压表示 [ 0 , 9 ] [0,9] [0,9]出现的次数,最开始设为0,用三进制的状态来表示, 1 1 1表示奇数次, 2 2 2表示偶数次, 0 0 0表示从未出现,然后跑数位 d p dp dp即可。

#include<bits/stdc++.h>
using namespace std;
typedef  long long ll;
const int N=75,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
ll l,r,dp[N][60000][2][2];
ll a[N],b[30];
int  f(int s,int p){
	return (s%b[p+1])/b[p];
}
bool jg(int s){
	for(int i=0;i<=9;i++){
		if((i&1)&&(f(s,i)==1)) return 0;
		 if((i%2==0)&&f(s,i)==2) return 0; 
	}
	return 1;
}
ll dfs(int x,int s,bool li,bool lead){
	ll ans=0;
	int mx;
	if(!x) return jg(s);
	if(~dp[x][s][li][lead]) return dp[x][s][li][lead];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++){
		ll tmp;
		if(lead&&!i) tmp=0;
		else {
			if(f(s,i)==2) tmp=-b[i];
			else tmp=b[i];
		} 
		ans+=dfs(x-1,s+tmp,li&(i==mx),lead&&(!i));
	}
	return dp[x][s][li][lead]=ans;
}
ll fun(ll n){
	int w=0;
	mst(dp,-1);
	while(n) a[++w]=n%10,n/=10;
	return dfs(w,0,1,1);
}
int main(){
	int T;
	scanf("%d",&T);
	b[0]=1;
	for(int i=1;i<=11;i++) b[i]=b[i-1]*3;
	while(T--){
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",fun(r)-fun(l-1));
	}
	return 0;
}

P4124 [CQOI2016]手机号码

题意:求区间满足存在至少三个相邻数位相同且不能同时包含 8 , 4 8,4 8,4的数的个数。

比较套路,用两个 p r e pre pre维护下相邻数位,然后状压维护是否同时包含 8 , 4 8,4 8,4,跑数位 d p dp dp即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
ll l,r;
int a[N];
ll dp[N][10][10][4][2][2][2];
ll dfs(int x,int pre,int ppre,int st,bool jg,bool lead,bool li){
	ll ans=0,mx;
	if(!x) return jg&&(st!=3);
	if(~pre&&~ppre&&~dp[x][pre][ppre][st][jg][lead][li]) return dp[x][pre][ppre][st][jg][lead][li];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++){
		int tmp;
		if(i==8&&st<2) tmp=2;
		else if(i==4&&(st==2||st==0)) tmp=1;
		else tmp=0; 
		ans+=dfs(x-1,i,!lead?pre:-1,st+tmp,jg|(i==pre&&i==ppre&&i)|(!i&&!pre&&!ppre&&!lead),lead&(!i),li&(i==mx));
	}
	if(~pre&&~ppre) dp[x][pre][ppre][st][jg][lead][li]=ans;
	return ans;
}
ll fun(ll n){
	int w=0;
	while(n) a[++w]=n%10,n/=10;
	mst(dp,-1);
	return dfs(w,-1,-1,0,0,1,1);
}
int main(){
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",fun(r)-fun(l-1));
	return 0;
}

P4127 [AHOI2009]同类分布

题意:求区间满足数位和能整除该数的数个数。

思路:关键就是枚举模数然后求和,因为模数和的范围很小,所以可以暴力枚举,每次跑数位 d p dp dp

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=75,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
#define rgi register int
ll l,r;
ll dp[20][170][170][2];
int a[20],m;
inline ll dfs(int x,int y,int s,bool li){
	if(s+x*9<m) return 0;
	register ll ans=0;
	rgi mx;
	if(~dp[x][y][s][li]) return dp[x][y][s][li];
	if(!x) return !y&&(s==m);
	mx=li?a[x]:9;
	for(int i=0;i<=mx&&s+i<=m;i++){
		ans+=dfs(x-1,(y*10+i)%m,s+i,li&(i==mx));
	}
	return  dp[x][y][s][li]=ans;
}
inline ll fun(ll n){
	 rgi w=0;
	register ll ans=0;
	while(n){
		a[++w]=n%10,n/=10;
	}
	for( m=1;m<=9*w;m++){
		mst(dp,-1);
		ans+=dfs(w,0,0,1);
	}
	return ans;
}
int main(){
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",fun(r)-fun(l-1));
	return 0;
}

P6754 [BalticOI 2013 Day1] Palindrome-Free Numbers

题意:求区间内不包含回文子串的数字和。

思路:正难则反,用两个 p r e , p p r e pre,ppre pre,ppre维护下,然后跑数位 d p dp dp即可,注意要在 f u n ( ) fun() fun()里面初始化。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
ll l,r;
int a[N];
ll dp[N][10][10][2][2][2];
inline ll dfs(int x,int pre,int ppre,bool st,bool lead,bool li){
	ll ans=0,mx;
	if(!x) return st;
	if(~pre&&~ppre&&~dp[x][pre][ppre][st][lead][li]) return dp[x][pre][ppre][st][lead][li];
	mx=li?a[x]:9;
	for(int i=0;i<=mx;i++){
		ans+=dfs(x-1,i,!lead?pre:-1,st|(!lead&&((i==pre)||(i==ppre))),lead&(!i),li&(i==mx));
	}
	if(~pre&&~ppre) dp[x][pre][ppre][st][lead][li]=ans;
	return ans;
}
inline ll fun(ll n){
	int w=0;
	mst(dp,-1);
	while(n) a[++w]=n%10,n/=10;
	return dfs(w,-1,-1,0,1,1);
}
int main(){
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",r-l+1-(fun(r)-fun(l-1)));
	return 0;
}

数位dp习题_i++

传送门

a ∣ b = a + b a|b=a+b ab=a+b,即 a , b a,b a,b二进制位不能同时为1。因为 1 ≤ b ≤ x 1\le b\le x 1bx,所以考虑数位 d p dp dp

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb push_back
ll dp[N][2],a[N],b[N];
inline ll dfs(int x,bool li){
    if(!x) return 1;
    if(~dp[x][li]) return dp[x][li];
    ll ans=0,mx=li?a[x]:1;
    for(int i=0;i<=mx;i++){
        if(b[x]&&i) continue;
        ans+=dfs(x-1,li&(i==mx));
    }
    return dp[x][li]=ans;
}
inline ll fun(ll x,ll y){
    int w=0;
    while(y) a[++w]=y%2,y>>=1;
    for(int i=0;i<31;i++) b[i+1]=(x>>i&1);
    return dfs(w,1);
}
int main(){
    int t;scanf("%d",&t);
    while(t--){
        mst(dp,-1);
        ll a,x;scanf("%lld%lld",&a,&x);
        printf("%lld\n",fun(a,x)-1);
    }
	return 0;
}