数位dp
数位dp
T3:
应该先从这道题入手数位dp,首先理解数位dp,dp了什么东西
它是把一个数从低位拆到高位,相当于是较低位的方案数是较高位的子问题
个人认为数位dp写记忆化搜索更容易理解一点
看这道题,首先我们让我们分别求 \(0~9\) 在 \(l~r\) 出现的次数,我们先把 \(0~9\) 分别求,再把 \(l~r\) 差分一下,就是用 \(1~r\) 减去 \(1~l-1\)
编码时,我们用now来存储统计0~9中的一个数字,下面以now=2为例
我们存一下 \(dp[pos][sum]\) 表示最后pos位,pos位前面前面有sum个数=now
举个例子:
然后我们下传时要用lead表示是否有前导0,limit表示当前最高位是否有数位限制
个人理解需要下传是否有前导0的原因是因为我们要在统计0出现的个数,而不能把前导0也给统计上,此处仅为本人个人想法,如有纠正,请发表评论
ps:已经证明了,我将所有lead删去之后,其余数位全部正确,只有统计0的个数时出现错误了,说明下传前导0,只与统计0时不能统计上前导0有关
记搜上代码(更容易理解一些):
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int l,r,now;
int num[N],dp[N][N];
int dfs(int pos,int sum,bool lead,bool limit){
int ans=0;
if(pos==0) return sum;//说明已经精确到一个数了,直接返回这个数中的now个数
if(!lead&&!limit&&dp[pos][sum]!=-1) return dp[pos][sum];//因为我们记忆化的是没有前导0,没有限制的大众情况
int up=9;
if(limit) up=num[pos];//up表示这一位最高到哪里,没有限制就是9
for(int i=0;i<=up;i++){
if(i==0&&lead) ans+=dfs(pos-1,sum,1,limit&&i==up);//下传前导0情况
else if(i==now) ans+=dfs(pos-1,sum+1,0,limit&&i==up);//这一位出现了now
else ans+=dfs(pos-1,sum,0,limit&&i==up);
}
if(!lead&&!limit) dp[pos][sum]=ans;//记忆化搜索
return ans;
}
int solve(int x){
int len=0;
while(x){
num[++len]=x%10;
x/=10;
}
memset(dp,-1,sizeof(dp));
return dfs(len,0,1,1);
}
signed main(){
scanf("%lld%lld",&l,&r);
for(int i=0;i<=9;i++){
now=i;
printf("%lld ",solve(r)-solve(l-1));
}
}
T1:
就这题还放T1,它什么难度,你心里没点B数吗
B数要满足两个限制,一个是要出现13,另一个是要被13整除
也就是说我们只有满足这个条件才能被统计到,就是转化一下,T3是出现now可以被统计,我们这个只不过是更复杂了一些,所以判断时也更复杂,但是没有本质区别
对于除以13的余数这个玩意,考虑从上一位下传到下一位,是把上一位除以13得到的余数*10再加上这一位的数在模上13,就是这一位剩下的余数,所以我们下传一个这一位的余数y
对于是否包含13这个限制我们设三种状态,0没有任何包含,1上一位是1,2已经包含13,然后我们根据这个状态转移就好了
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int n;
int num[N],dp[N][N][N];
int check(int x,int c){
if(c==2) return 2;//注(卡了我好久):只要有13,就可以算,所以放在最前面
if(x==1) return 1;
if(c==1&&x==3) return 2;
return 0;
}
int dfs(int pos,int y,int c,int limit){
int ans=0;
if(!pos) return !y&&c==2;//判断这个数是否位B数
if(!limit&&dp[pos][y][c]!=-1) return dp[pos][y][c];///记搜
int up=9;
if(limit) up=num[pos];
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,(y*10+i)%13,check(i,c),limit&&i==up);
}
if(!limit) dp[pos][y][c]=ans;
return ans;
}
signed main(){
while(scanf("%lld",&n)!=EOF){
int len=0;
while(n){
num[++len]=n%10;
n/=10;
}
memset(dp,-1,sizeof(dp));
printf("%lld\n",dfs(len,0,0,1));
}
return 0;
}
T2:
切了,和T1思路基本一样,先把n转化为二进制,只需要下传二进制数位0/1的个数即可,注意也要处理前导0情况不能算
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=50;
int l,r;
int num[N],dp[N][N][N];
int dfs(int pos,int sum0,int sum1,int lead,int lim){
if(!pos) return sum0>=sum1;
if(!lead&&!lim&&dp[pos][sum0][sum1]!=-1) return dp[pos][sum0][sum1];
int up=1,ans=0;
if(lim) up=num[pos];
for(int i=0;i<=up;i++){
if(i==0&&lead) ans+=dfs(pos-1,sum0,sum1,1,lim&&i==up);
else if(i==0) ans+=dfs(pos-1,sum0+1,sum1,0,lim&&i==up);
else ans+=dfs(pos-1,sum0,sum1+1,0,lim&&i==up);
}
if(!lead&&!lim) dp[pos][sum0][sum1]=ans;
return ans;
}
int solve(int x){
int len=0;
while(x){
num[++len]=x%2;
x/=2;
}
memset(dp,-1,sizeof(dp));
return dfs(len,0,0,1,1);
}
signed main(){
scanf("%lld%lld",&l,&r);
printf("%lld",solve(r)-solve(l-1));
}
ps:此题如果wa了的话,会发现显示数据是错误的,但是wa了跟数据无关,如果是90分可以看一下是不是空间开小了
T5:
正确的做法lz懒得写,直接二分切了
我们显然容易判断 \(1~x\) 中幸运数字的个数,然后就拿这个二分一下就可以做了
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=60,M=1e10;
int T,n;
int dp[N][4],num[N];
int pan(int x,int c){
if(c==3) return 3;
if(x==6) return c+1;
return 0;
}
int dfs(int pos,int c,bool lim){
if(!pos) return c==3;
if(!lim&&dp[pos][c]!=-1) return dp[pos][c];
int up=9,ans=0;
if(lim) up=num[pos];
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,pan(i,c),lim&&i==up);
}
if(!lim) dp[pos][c]=ans;
return ans;
}
int check(int x){
int len=0;
while(x){
num[++len]=x%10;
x/=10;
}
memset(dp,-1,sizeof(dp));
return dfs(len,0,1);
}
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld",&n);
int l=666,r=M;
while(l<r){
int mid=(l+r)/2;
if(check(mid)>=n) r=mid;
else l=mid+1;
}
printf("%lld\n",l);
}
}
T6:
直接切了
最板子的数位dp,然后把ans+=,改为ans*=就过了,最后再判断掉sum(0)的情况就行了
写完后喵了一眼题解,不懂啊为什么要用快速幂,既复杂又慢,像个小丑🤡
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=60,mod=1e7+7;
int n;
int dp[N][N],num[N];
int dfs(int pos,int sum,bool lim){
if(!pos) return sum;
if(!lim&&dp[pos][sum]!=-1) return dp[pos][sum];
int up=1,ans=1;
if(lim) up=num[pos];
for(int i=0;i<=up;i++){
if(sum==0&&pos==1&&i==0) continue;//特判0的情况
ans*=dfs(pos-1,sum+(i==1),lim&&i==up);//加改为乘
ans%=mod;
}
if(!lim) dp[pos][sum]=ans;
return ans;
}
signed main(){
scanf("%lld",&n);
int len=0;
while(n){
num[++len]=n%2;
n/=2;
}
memset(dp,-1,sizeof(dp));
printf("%lld",dfs(len,0,1));
}