数位dp
数位dp
T3:
应该先从这道题入手数位dp,首先理解数位dp,dp了什么东西
它是把一个数从低位拆到高位,相当于是较低位的方案数是较高位的子问题
个人认为数位dp写记忆化搜索更容易理解一点
看这道题,首先我们让我们分别求
编码时,我们用now来存储统计0~9中的一个数字,下面以now=2为例
我们存一下
举个例子:
然后我们下传时要用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懒得写,直接二分切了
我们显然容易判断
代码:
点击查看代码
#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));
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!