数位dp模板

数位dp

dp[pos][flag][limit] 代表从高到低到了第pos位置,从第pos位到第0位之间在状态位flag的情况下是否有limit条件下的答案是多少,以不要62这道题目为例子在这里比方说都是到了第万位数,需要确定的只有前面是否是6,其他的答案是一样多的,并且需要注意的是只有没有limit限制的时候才可以进行记忆化搜索,因为如果有limit限制的话,这个情况只会出现一次,存下来是没有用的

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+5;
int dp[maxn][2];
int digit[maxn];
int len;
void cal(int n){
	memset(dp,-1,sizeof(dp));
	len=0;
	while(n){
		digit[++len]=n%10;
		n/=10;
	}
}
int dfs(int pos,int flag,int limit){
	int ret=0;
	if(!pos){
		return 1;
	}
	if(!limit&&dp[pos][flag]!=-1){
		return dp[pos][flag];
	}
	int up=limit?digit[pos]:9;
	for(int i=0;i<=up;i++){
		if(i==4&&flag||i==2){
			continue;
		}
		ret+=dfs(len-1,i==6,limit&&i==up);
	}
	if(!limit){
		dp[pos][flag]=ret;
	}
	return ret;
}
signed main(){
	int x,y;
	while(cin>>x>>y){
		cal(y);
		int you=dfs(len,0,1);
		cal(x-1);
		int zuo=dfs(len,0,1);
		cout<<you-zuo<<"\n";
	}
}

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e2+5;
int digit[maxn],dp[maxn][2][2];
int len,need;
int ans1[maxn],ans2[maxn],w[maxn];
void cal(int x){
	len=0;
	int sta=1;
	while(x){
		digit[++len]=x%10;
		x/=10;
		w[len]=w[len-1]+digit[len]*sta;
		sta*=10;
	}
	if(len==0){
		digit[++len]=0;
	}
}
int dfs(int pos,int check,int limit){
	if(pos==0){
		return 0;
	}
	if(!limit&&dp[pos][check][limit]!=-1){
		return dp[pos][check][limit];
	}
	int up=limit?digit[pos]:9;
	int res=0;
	for(int i=0;i<=up;i++){
		res+=dfs(pos-1,check&&i==0,limit&&i==up);
		if(i==need){
			if(check&&need==0){
				if(pos==1){
					res++;
				}
			}
			else{
				if(limit&&i==up){
					res+=w[pos-1]+1;
				}
				else{
					res+=pow(10,pos-1);
				}
			}
		}
	}
	dp[pos][check][limit]=res;
	return res;
}
signed main(){
	int x,y;
	while(cin>>x>>y){
		if(x==0&&y==0){
			return 0;
		}
		if(x>y){
			swap(x,y);
		}
		cal(y);
		for(int i=0;i<=9;i++){
			for(int j=0;j<=100;j++){
				for(int check=0;check<=1;check++){
					for(int limit=0;limit<=1;limit++){
						dp[j][check][limit]=-1;
					}
				}
			}
			need=i;
			ans2[i]=dfs(len,1,1);
		}
		cal(x-1);
		for(int i=0;i<=9;i++){
			for(int j=0;j<=100;j++){
				for(int check=0;check<=1;check++){
					for(int limit=0;limit<=1;limit++){
						dp[j][check][limit]=-1;
					}
				}
			}
			need=i;
			ans1[i]=dfs(len,1,1);
			cout<<ans2[i]-ans1[i]<<' ';
		}
		cout<<"\n";
	}
}

2020ICPC上海站C题

通过分析可以知道其实就是i从0-x,j从0-y,并且i和j不能同时为0时式子的和是多少,然后对于i和j,它们相与为0,对答案的贡献是他们最高位的1为位数是第几位,比如i等于1001,j等于0100,那么最高位是i的第四个1,这个i和j对于答案的贡献就是4,可以设计dp方程dp[pos][x][y]代表到了第pos位i和j没有相同时,i选择为x,j选择为y有多少种组合

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=2e5+5;
const int inf=1e9+7;
const int mod=1e9+7;
int x[32],y[32],cnt1,cnt2;
int dp[32][2][2];  //代表最高位是第pos位,目前f1=i,f2=j有多少种选择
int a,b;
int dfs(int pos,int f1,int f2){
	if(!pos){
		return 1;
	}
	if(dp[pos][f1][f2]!=-1){
		return dp[pos][f1][f2];
	}
	int upa=f1?x[pos]:1;
	int upb=f2?y[pos]:1;
	int res=0;
	for(int i=0;i<=upa;i++){
		for(int j=0;j<=upb;j++){
			if((i&j)==0){
				res=(res+dfs(pos-1,f1&&i==upa,f2&&j==upb))%mod;
			}
		}
	}
	return dp[pos][f1][f2]=res;
}
int init(){
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	memset(dp,-1,sizeof(dp));
	cnt1=0,cnt2=0;
	while(a){
		x[++cnt1]=a&1;
		a>>=1;
	}
	while(b){
		y[++cnt2]=b&1;
		b>>=1;
	}
	int res=0;
	for(int i=1;i<=max(cnt1,cnt2);i++){
		if(i<=cnt1) res=(res+dfs(i-1,i==cnt1,i>cnt2)*i%mod)%mod;  //代表最高位为1的是a
		if(i<=cnt2) res=(res+dfs(i-1,i>cnt1,i==cnt2)*i%mod)%mod;  //代表最高位为1的是b
	}
	return res;
}
void solve(){
	cin>>a>>b;
	cout<<init()<<"\n";
}
signed main(){
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}
}

恨7不是妻

题目描述:

给定L,R,问[L,R]的与7无关的数的平方的和是多少

思路:

在有限的空间内,用dp可以记录下来的前面的状态有各个位上的数的和的大小(%7)shuhe,以及前面的数的大小(%7)sumhe,用这两个条件显然不能求出答案,考虑把一个数分为2部分,123abc,那么假设现在到了a这个位置,我们前面是123,后面随意组合,则dp[pos][shuhe][sumhe]可以记录一下他到最低位的有效的数的平方和sum2,他到最低位的有效的数的和sum1,以及他到最低为的有效的数的个数cnt,对sum2的贡献即为(123*10^3)^2+(2*123*10^3*abc)+(abc)^2,细节再考虑一下怎么记录下来就好

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=30;
const int inf=1e9+7;
const int mod=1e9+7;
int p[maxn];
int cnt=0;
int digit[maxn];
struct node{
	int cnt,sum1,sum2;
	//sum1是数的和,sum2是数的平方和
};
void cal(int x){
	cnt=0;
	while(x){
		digit[++cnt]=x%10;
		x/=10;
	}
}
node dp[maxn][10][10];
node dfs(int pos,int weihe,int sumhe,int flag){
	if(pos==0){
		return {weihe&&sumhe,0,0};
	}
	if(!flag&&dp[pos][weihe][sumhe].cnt!=-1){
		return dp[pos][weihe][sumhe];
	}
	int upper=flag?digit[pos]:9;
	node res={0,0,0};
	for(int i=0;i<=upper;i++){
		if(i==7){
			continue;
		}
		else{
			node tmp=dfs(pos-1,(weihe+i)%7,(sumhe*10+i)%7,flag&&i==upper);
			res.cnt+=tmp.cnt;
			res.cnt%=mod;
			int now=i*p[pos-1]%mod;
			res.sum1+=tmp.sum1+now*tmp.cnt;
			res.sum1%=mod;
			res.sum2+=now*i%mod*p[pos-1]%mod*tmp.cnt%mod+2*now*tmp.sum1%mod+tmp.sum2%mod;
			res.sum2%=mod;
			
		}
	}
	if(!flag){
		dp[pos][weihe][sumhe]=res;
	}
	return res;
}
void init(){
	p[0]=1;
	for(int i=1;i<=20;i++){
		p[i]=p[i-1]*10%mod;
	}
}
void solve(){
	int l,r;
	cin>>l>>r;
	cal(r);
	for(int i=0;i<=29;i++){
		for(int j=0;j<=9;j++){
			for(int k=0;k<=9;k++){
				dp[i][j][k].cnt=-1;
				dp[i][j][k].sum1=0;
				dp[i][j][k].sum2=0;
			}
		}
	}
	int ans2=dfs(cnt,0,0,1).sum2;
	cal(l-1);
	for(int i=0;i<=29;i++){
		for(int j=0;j<=9;j++){
			for(int k=0;k<=9;k++){
				dp[i][j][k].cnt=-1;
				dp[i][j][k].sum1=0;
				dp[i][j][k].sum2=0;
			}
		}
	}
	int ans1=dfs(cnt,0,0,1).sum2;
	cout<<(ans2+mod-ans1)%mod<<"\n";
}
signed main(){
	init();
	int t=1;
	cin>>t;
	while(t--){
		solve();
	}
}

2022CCPC广州站M

思路:

数位dp记录dp[cur][s][lv] cur:当前位数 s:当前有多少个数顶着上界 lv:还需要加多少值才能等于k

对于每一位枚举其1的个数和0的个数,乘以这样分配有多少种方法,即可算出这一位的贡献然后加一定的剪枝即可过题

#include<bits/stdc++.h>
#define int long long
#define io ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int maxn=105;
const int inf=1e9+7;
const int mod=1e9+7;
map<int,int>dp[maxn][maxn];
int c[maxn][maxn];
int digit[maxn];
int cnt;
void cal(int x){
	while(x){
		digit[++cnt]=x%2;
		x/=2;
	}
}
int n,m,k;
int dfs(int cur,int s,int lv){  //s:当前有多少个数顶着上界 lv:还需要加多少值才能等于k
	if(lv<0){
		return 0;
	}
	if(cur==0){
		return (lv==0);
	}
	int s1=k/2,s0=k-s1;
	if(((1ll<<cur)-1)*s0*s1<lv){
		return 0;
	}
	if(dp[cur][s].count(lv)){
		return dp[cur][s][lv];
	}
	int res=0;
	if(digit[cur]==1){
		//当前位上界是1
		for(int i=0;i<=s;i++){
			//选择i个赋值为1
			for(int j=0;j<=k-s;j++){
				//选择j个赋值为1
				int val=1ll*(k-i-j)*(i+j)*(1ll<<(cur-1));
				int t=c[s][i]*c[k-s][j]%mod;
				res+=t*dfs(cur-1,i,lv-val)%mod;
				res%=mod;
			}
		}
	}
	else{ 
		for(int i=0;i<=0;i++){ //顶着上界的数一个都不能选 
			for(int j=0;j<=k-s;j++){ //枚举现在选择多少1
				int val=1ll*(k-i-j)*(i+j)*(1ll<<(cur-1));
				int t=c[s][i]*c[k-s][j]%mod;
				res+=t*dfs(cur-1,s,lv-val)%mod;
				res%=mod;
			}
		}
	}
	return dp[cur][s][lv]=res;
}
void solve(){
	for(int i=0;i<=100;i++){
		for(int j=0;j<=i;j++){
			if(i==0||j==0){
				c[i][j]=1;
			}
			else{
				c[i][j]=c[i-1][j]+c[i-1][j-1];
			}
		}
	}
	cin>>n>>m>>k;
	cal(m);	
	cout<<dfs(cnt,k,n)<<"\n";
}
signed main(){
	int t=1;
	//cin>>t;
	while(t--){
		solve();
	}
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值