数位dp~~


前言

其实对于数位dp而言,没必要执着于做到dp的形式,做得dfs+记忆化的程度就足够了,这样能够降低写代码的难度。

1.最大为N的数字组合

https://leetcode.cn/problems/numbers-at-most-n-given-digit-set/description/
在这里插入图片描述
题解:

class Solution {
public:

    int atMostNGivenDigitSet(vector<string>& str, int n) {
        int tmp=n/10,len=1,offset=1;
        while(tmp){
            tmp/=10;
            len++;
            offset*=10;
        }
        int m=str.size();
        vector<int>digits(m);
        for(int i=0;i<m;i++){
            digits[i]=stoi(str[i]);
        }
        int cnt[11];
        cnt[0]=1;
        int ans=0;
        for(int i=m,k=1;k<len;k++,i*=m){
            cnt[k]=i;
            ans+=i;
        }
        return ans+f1(digits,cnt,len,n,offset);
    }
    int f1(vector<int>digits,int cnt[],int len,int n,int offset){
        if(len==0)return 1;
        int ans=0;
        int cur=(n/offset)%10;
        for(int i:digits){
            if(i<cur)ans+=cnt[len-1];
            else if(i==cur)ans+=f1(digits,cnt,len-1,n,offset/10);
            else break;
        }
        return ans;
    }
    
};

今天又新学到一个函数:stoi,可以把string转成int,好耶好耶~

2.统计整数数目


https://leetcode.cn/problems/count-of-integers/description/

在这里插入图片描述

class Solution {
public:
    int min1,max1,len;
    int num[23];
    int mem[23][500][2];
    int N=1e9+7;
    int count(string n1, string n2, int min_sum, int max_sum) {
        min1=min_sum,max1=max_sum;
        int tp=0;
        len=n1.length();
        for(int i=0;i<len;i++){
            num[i]=n1[i]-'0';
            tp+=num[i];
        }
        int ans=f1(0,0,0)%N;
        memset(mem,0,sizeof mem);
        len=n2.length();
        for(int i=0;i<len;i++){
            num[i]=n2[i]-'0';
        }
        ans=(f1(0,0,0)%N-ans+N)%N;
        return tp>=min1 && tp<=max1 ? (ans+1):ans;
    }
    int f1(int cur,int sum,int free){
        if(sum>max1)return 0;
        if(sum+(len-cur)*9<min1)return 0;
        if(cur==len)return (sum>=min1)&&(sum<=max1)?1:0;
        if(mem[cur][sum][free])return mem[cur][sum][free];
        int ans=0;
        if(free==0){
            for(int i=0;i<=9;i++){
                if(i<num[cur])ans=(f1(cur+1,sum+i,1)+ans)%N;
                else if(i==num[cur])ans=(ans+f1(cur+1,sum+i,0))%N;
                else break;
            }
        }else{
            for(int i=0;i<=9;i++){
                ans=(ans+f1(cur+1,sum+i,1))%N;
            }
        }
        mem[cur][sum][free]=ans;
        return ans;
    }
};

取模:ans=(ans+mod)%mod
求a~b的范围等价与0~b的数减去0~a-1的数

3.统计特殊整数


https://leetcode.cn/problems/count-special-integers/description/

在这里插入图片描述

class Solution {
public:
    int mem[11][2][1<<10];
    int countSpecialNumbers(int n) {
        int tmp=n/10,len=1,offset=1;
        while(tmp){
            tmp/=10;
            len++;
            offset*=10;
        }
        if(len==1)return n;
        int ans=9;
        for(int i=9,k=1;k<len-1;k++){
            i=i*(10-k);
            ans+=i;
        }
        return ans+f(len,len,0,n,offset,0);
    }
    int f(int len,int curlen,int free,int n,int offset,int status){
        if(curlen==0)return 1;
        if(mem[curlen][free][status])return mem[curlen][free][status];
        int cur=(n/offset)%10;
        int ans=0;
        if(free){
            int k1=10-len+curlen,k2=1,m=curlen;
            while(m){
                k2*=k1;
                k1--;
                m--;
            }
            ans+=k2;
        }
        else{
            if(len==curlen){
                for(int i=1;i<cur;i++){
                    if(!((1<<i)&status)){
                        ans+=f(len,curlen-1,1,n,offset/10,status^(1<<i));
                    }
                }
                if(!((1<<cur)&status)){
                    ans+=f(len,curlen-1,0,n,offset/10,status^(1<<cur));
                }
            }else{
                for(int i=0;i<cur;i++){
                    if(!((1<<i)&status)){
                        ans+=f(len,curlen-1,1,n,offset/10,status^(1<<i));
                    }
                }
                if(!((1<<cur)&status)){
                    ans+=f(len,curlen-1,0,n,offset/10,status^(1<<cur));
                }
            }
        }
        mem[curlen][free][status]=ans;
        return ans;
    }
};

4.[SCOI2009] windy 数

题目背景

windy 定义了一种 windy 数。

题目描述

不含前导零且相邻两个数字之差至少为 2 2 2 的正整数被称为 windy 数。windy 想知道,在 a a a b b b 之间,包括 a a a b b b ,总共有多少个 windy 数?

输入格式

输入只有一行两个整数,分别表示 a a a b b b

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

1 10

样例输出 #1

9

样例 #2

样例输入 #2

25 50

样例输出 #2

20

提示

数据规模与约定

对于全部的测试点,保证 1 ≤ a ≤ b ≤ 2 × 1 0 9 1 \leq a \leq b \leq 2 \times 10^9 1ab2×109

#include<bits/stdc++.h>
using namespace std;
#define N 30
int len1=1,len2=1,offset1=1,offset2=1;//len记录几位,offset辅助用于截取
int a,b;
//q==10代表前面都是都没选数字
int mem[11][11][2];
int f(int q,int len,int offset,int free1,int n){//free1:怎么选都比上限小,q==10:不用考虑和前一位差至少为2
	if(len==0)return 1;
	if(mem[q][len][free1])return mem[q][len][free1];
	int ans=0;
	int cur=(n/offset)%10;
	if(free1){
		if(q==10){
			ans+=f(10,len-1,offset/10,1,n);
			for(int i=1;i<=9;i++){
				ans+=f(i,len-1,offset/10,1,n);
			}
		}else{
			for(int i=0;i<=9;i++){
				if(abs(q-i)>=2)ans+=f(i,len-1,offset/10,1,n);
			}
		}
	}else{
		if(q==10){
			if(len!=1)ans+=f(10,len-1,offset/10,1,n);
			for(int i=1;i<cur;i++){
				ans+=f(i,len-1,offset/10,1,n);
			}
			ans+=f(cur,len-1,offset/10,0,n);
		}else{
			for(int i=0;i<cur;i++){
				if(abs(q-i)>=2)ans+=f(i,len-1,offset/10,1,n);
			}
			if(abs(q-cur)>=2)ans+=f(cur,len-1,offset/10,0,n);
		}
	}
	mem[q][len][free1]=ans;
	return ans;
}
int main(){
	scanf("%d %d",&a,&b);
	int tmp=(a-1)/10;
	while(tmp){
		tmp/=10;
		len1++;
		offset1*=10;
	}
	tmp=b/10;
	while(tmp){
		tmp/=10;
		len2++;
		offset2*=10;
	}
	int ans1=f(10,len2,offset2,0,b);
	memset(mem,0,sizeof mem);
	int ans=ans1-f(10,len1,offset1,0,a-1);
	cout<<ans;
	return 0;
}

5.SAC#1 - 萌数

题目背景

本题由世界上最蒟蒻的 SOL 提供。

寂月城网站是完美信息教室的官网。地址:http://191.101.11.174/mgzd

题目描述

蒟蒻 SOL 居然觉得数很萌!

好在在他眼里,并不是所有数都是萌的。只有满足“存在长度至少为 2 2 2 的回文子串”的数是萌的——也就是说, 101 101 101 是萌的,因为 101 101 101 本身就是一个回文数; 110 110 110 是萌的,因为包含回文子串 11 11 11;但是 102 102 102 不是萌的, 1201 1201 1201 也不是萌的。

现在 SOL 想知道从 l l l r r r 的所有整数中有多少个萌数。

由于答案可能很大,所以只需要输出答案对 1000000007 1000000007 1000000007 1 0 9 + 7 10^9+7 109+7)的余数。

输入格式

输入包含仅 1 1 1 行,包含两个整数: l l l r r r

输出格式

输出仅 1 1 1 行,包含一个整数,即为答案。

样例 #1

样例输入 #1

1 100

样例输出 #1

10

样例 #2

样例输入 #2

100 1000

样例输出 #2

253

提示

n n n r r r 10 10 10 进制下的位数。

对于 10 % 10\% 10% 的数据, n ≤ 3 n \le 3 n3

对于 30 % 30\% 30% 的数据, n ≤ 6 n \le 6 n6

对于 60 % 60\% 60% 的数据, n ≤ 9 n \le 9 n9

对于全部的数据, n ≤ 1000 n \le 1000 n1000 l < r l < r l<r


2024/2/4 添加一组 hack 数据。

正向思路的解:

#include<bits/stdc++.h>
using namespace std;
#define N 30
int mod=1e9+7;
string l,r;
int l1[1001],r1[1001];
int len1,len2;
int mem[11][11][2001][2][2];//记得初始化
int quickmod(int x){
	long long base=1;
	for(int i=0;i<x;i++){
		base=(base*10)%mod;
	}
	return (int)base;
}
int f(int qq,int q,int cur,int len,int free1,int free2,int num[]){//free1:不考虑大小free2:不考虑回文
	if(cur==len) return free2==1 ? 1:0;
	int ans=0;
	int m=num[cur];
	if(mem[qq][q][cur][free1][free2])return mem[qq][q][cur][free1][free2];
	if(free1){
		if(q==10){
			ans=(ans+f(10,10,cur+1,len,1,0,num))%mod;
			for(int i=1;i<=9;i++){
				ans=(ans+f(10,i,cur+1,len,1,0,num))%mod;
			}
		}else{
			if(free2){
				ans=(ans+quickmod(len-cur))%mod;
			}else{
				for(int i=0;i<=9;i++){
					if(i==q || i==qq)ans=(ans+f(q,i,cur+1,len,1,1,num))%mod;
					else ans=(ans+f(q,i,cur+1,len,1,0,num))%mod;
				}
			}
		}
	}else{
		if(q==10){
			ans=(ans+f(10,10,cur+1,len,1,0,num))%mod;
			for(int i=1;i<m;i++){
				ans=(ans+f(10,i,cur+1,len,1,0,num))%mod;
			}
			ans=(ans+f(10,m,cur+1,len,0,0,num))%mod;
		}else{
			if(free2){
				for(int i=0;i<m;i++){
					ans=(ans+f(q,i,cur+1,len,1,1,num))%mod;
				}
				ans=(ans+f(q,m,cur+1,len,0,1,num))%mod;
			}else{
				for(int i=0;i<m;i++){
					if(i==q || i==qq)ans=(ans+f(q,i,cur+1,len,1,1,num))%mod;
					else ans=(ans+f(q,i,cur+1,len,1,0,num))%mod;
				}
				if(m==q || m==qq)ans=(ans+f(q,m,cur+1,len,0,1,num))%mod;
				else ans=(ans+f(q,m,cur+1,len,0,0,num))%mod;
			}
		}
	}
	mem[qq][q][cur][free1][free2]=ans;
	return ans;

}

bool check(int l1[]){
	if(len1==1)return false;
	if(l1[0]==l1[1])return true;
	for(int i=2;i<len1;i++){
		if(l1[i]==l1[i-1] || l1[i]==l1[i-2])return true;
	}
	return false;
}
int main(){
	cin>>l>>r;
	len1=l.length(),len2=r.length();
	for(int i=0;i<len1;i++){
		l1[i]=l[i]-'0';
	}
	for(int i=0;i<len2;i++){
		r1[i]=r[i]-'0';
	}
	int ans1=f(10,10,0,len1,0,0,l1);
	memset(mem,0,sizeof mem);
	int ans2=f(10,10,0,len2,0,0,r1);
	int ans=(ans2-ans1+mod)%mod;
	if(check(l1)){
		cout<<(ans+1)%mod<<endl;
	}else{
		cout<<ans<<endl;
	}
	return 0;
}

正向会超时

反向思路解:

#include<bits/stdc++.h>
using namespace std;
#define N 30
int mod=1e9+7;
string l,r;
int l1[1001],r1[1001];
int len1,len2;
int mem[11][11][2001][2];//记得初始化
int f(int qq,int q,int cur,int len,int free,int num[]){
	if(cur==len) return 1;
	int ans=0;
	int m=num[cur];
	if(mem[qq][q][cur][free])return mem[qq][q][cur][free];
	if(free){
		if(q==10){
			if(cur!=len-1)ans=(ans+f(10,10,cur+1,len,1,num))%mod;
			for(int i=1;i<=9;i++){
				ans=(ans+f(10,i,cur+1,len,1,num))%mod;
			}
		}else{
			for(int i=0;i<=9;i++){
				if(i!=qq && i!=q)ans=(ans+f(q,i,cur+1,len,1,num))%mod;
			}
		}
	}else{
		if(q==10){
			if(cur!=len-1)ans=(ans+f(10,10,cur+1,len,1,num))%mod;
			for(int i=1;i<m;i++){
				ans=(ans+f(10,i,cur+1,len,1,num))%mod;
			}
			ans=(ans+f(10,m,cur+1,len,0,num))%mod;
		}else{
			for(int i=0;i<m;i++){
				if(i!=qq && i!=q)ans=(ans+f(q,i,cur+1,len,1,num))%mod;
			}
			if(m!=qq && m!=q)ans=(ans+f(q,m,cur+1,len,0,num))%mod;
		}
	}
	mem[qq][q][cur][free]=ans;
	return ans;

}
int cnt(int num[],int len){
	long long all=0;
	long long base=1;
	for(int i=len-1;i>=0;i--){
		all=(all+base*num[i])%mod;
		base=(base*10)%mod;
	}
	int p=(all-f(10,10,0,len,0,num)+mod)%mod;
	memset(mem,0,sizeof mem);
	return p;
}
bool check(int l1[]){
	if(len1==1)return false;
	if(l1[0]==l1[1])return true;
	for(int i=2;i<len1;i++){
		if(l1[i]==l1[i-1] || l1[i]==l1[i-2])return true;
	}
	return false;
}
int main(){
	cin>>l>>r;
	len1=l.length(),len2=r.length();
	for(int i=0;i<len1;i++){
		l1[i]=l[i]-'0';
	}
	for(int i=0;i<len2;i++){
		r1[i]=r[i]-'0';
	}
	int ans1=cnt(r1,len2),ans2=cnt(l1,len1);
	int ans=(ans1-ans2+mod)%mod;
	
	if(check(l1)){
		cout<<(ans+1)%mod<<endl;
	}else{
		cout<<ans<<endl;
	}
	return 0;
}

6.[ZJOI2010] 数字计数

题目描述

给定两个正整数 a a a b b b,求在 [ a , b ] [a,b] [a,b] 中的所有整数中,每个数码(digit)各出现了多少次。

输入格式

仅包含一行两个整数 a , b a,b a,b,含义如上所述。

输出格式

包含一行十个整数,分别表示 0 ∼ 9 0\sim 9 09 [ a , b ] [a,b] [a,b] 中出现了多少次。

样例 #1

样例输入 #1

1 99

样例输出 #1

9 20 20 20 20 20 20 20 20 20

提示

数据规模与约定
  • 对于 30 % 30\% 30% 的数据,保证 a ≤ b ≤ 1 0 6 a\le b\le10^6 ab106
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ a ≤ b ≤ 1 0 12 1\le a\le b\le 10^{12} 1ab1012

貌似解法跟数位dp没啥关系乐乐乐乐~~

#include<bits/stdc++.h>
using namespace std;
#define int long long 
int a,b;
int f(int d,int n){
	int ans=0;
	for(int tmp=n,left,right=1,cur;tmp!=0;right*=10){
		left=tmp/10;
		cur=tmp%10;
		tmp/=10;
		if(d==0)left--;
		ans+=left*right;
		if(cur>d)ans+=right;
		else if(cur==d)ans+=n%right+1;
	}
	return ans;
}
signed main(){
	cin>>a>>b;
	for(int i=0;i<=9;i++){
		cout<<f(i,b)-f(i,a-1)<<' ';
	}
	return 0;
}

总结感悟

1.函数往往含参数:1.遍历到第几个数位 2.free:当前数位选择自不自由 3前置位
2.需要的参数==与状态转移方程相关的参数
3.函数含义往往为:已经决定好了之前的数位的前提下,剩余的数位可选的方案数。

  • 26
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值