动态规划·数位DP
数位DP
好东西(?),但我不会
直接上例题吧
呃呃呃:
- 后文中的
默认是从低位往高位数的 - 为了在
的限制内,而后面方案的数都是随便选的,所以第 位不会取到 的第 位(保不齐就超了) - 但这就会导致取不到
,所以要特判
【YbtOj】题解
A.B数计数
设状态
,表示没有“13” ,表示当前填的数为“3” ,表示已有“13”
其中
在统计答案时,因为我们状态设的相当于是“
详:设
这样统计是统计不到
统计答案形象化(结合代码食用):
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=21;
int n;
int f[N][N][N];
int power[N];
void dp()
{
memset(f,0,sizeof f);
f[0][0][0]=1;
for (int pos=1;pos<=10;pos++)//在填的数位
for (int i=0;i<=9;i++)//试填pos位上的数
for (int res1=0;res1<13;res1++)//前pos位的余数
{
int res2=(res1-i*power[pos-1]%13+13)%13;//不包含第pos位的余数
if (i!=3) f[pos][res1][0]+=f[pos-1][res2][0];
if (i!=1&&i!=3) f[pos][res1][0]+=f[pos-1][res2][1];
if (i==3) f[pos][res1][1]+=f[pos-1][res2][0]+f[pos-1][res2][1];
if (i==1) f[pos][res1][2]+=f[pos-1][res2][1];
f[pos][res1][2]+=f[pos-1][res2][2];
}
}
int solve(int n)
{
int op1=0,res1=0,ans=0;//op1,res1针对n
for (int pos=10;pos>=1;pos--)
{
int s=n/power[pos-1];
for (int i=s-1;i>=0;i--)
{
int op2,res2=(res1+i*power[pos-1])%13;//res1:包括第pos+1位 res2:包括第pos位的余数 针对n
if (op1!=2)
{
if (i==3&&op1==1) op2=2;
else if (i==1) op2=1;
else op2=0;
}
else op2=2;
int res3=(13-res2)%13;//对后面几位的限制
if (op2==2) ans+=f[pos-1][res3][0]+f[pos-1][res3][1]+f[pos-1][res3][2];
else if (op2==1) ans+=f[pos-1][res3][2]+f[pos-1][res3][1];
else ans+=f[pos-1][res3][2];
}
if (op1!=2)
{
if (s==3&&op1==1) op1=2;
else if (s==1) op1=1;
else op1=0;
}
res1=(res1+s*power[pos-1])%13;
n%=power[pos-1];
}
if (op1==2&&res1==0) ans++;//特判n
return ans;
}
signed main()
{
power[0]=1;
for (int i=1;i<=9;i++) power[i]=power[i-1]*10;
while (scanf("%lld",&n)!=EOF)
{
dp();
printf("%lld\n",solve(n));
}
return 0;
}
B.区间圆数
因为“圆数”和二进制状态有关,所以设的是二进制下的状态、在二进制的基础上转移的。设状态
在统计答案时,可以有一个类似“前缀和”的思想:计算
与上题类似,因为
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=35;
int l,r;
int f[N][N];//已填到第i位,共有j个0的数的个数
int s[N],cnt;
void dp()
{
f[0][0]=1;
for (int i=1;i<=32;i++)
for (int j=0;j<=i;j++)
{
if (!j) f[i][j]=f[i-1][j];
else f[i][j]=f[i-1][j]+f[i-1][j-1];
}
}
int solve(int n)
{
if (!n) return 0;
int cnt0=0,cnt1=0,ans=0,cnt=0;
while (n) s[++cnt]=n%2,n/=2;
for (int pos=cnt;pos>=1;pos--)
{
if (s[pos]&&pos!=cnt)//前面与n相同但这位为0的情况
{
cnt0++;
for (int i=0;i<=pos-1;i++)
{
if (i+cnt0>=cnt-cnt0-i) ans+=f[pos-1][i];
}
cnt0--;
}
if (pos!=cnt)//前面都是前导0的情况
{
for (int i=0;i<=pos-1;i++)
{
if (i>=pos-i) ans+=f[pos-1][i];
}
}
cnt0+=!s[pos];
cnt1+=s[pos];
}
if (cnt0>=cnt1) ans++;
return ans;
}
signed main()
{
dp();
scanf("%lld%lld",&l,&r);
int ans=solve(r)-solve(l-1);
printf("%lld",ans);
return 0;
}
C.数字计数
可以想到设状态
然后就会发现
直接转移就转移完了(感动哭
统计答案时,和上题同(“前缀和”思想)。然后统计就行
贴
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=16;
int l,r;
int power[N],f[N];//f[pos][j]:第pos位数码j出现的次数
int s[N],a[N],ans[N];
void dp()
{
power[0]=1;
for (int i=1;i<N;i++) power[i]=power[i-1]*10;
for (int pos=1;pos<N;pos++) f[pos]=f[pos-1]*10+power[pos-1];
}
void solve(int n)
{
memset(a,0,sizeof a);
int len=0,tmp=n;
while (tmp)//处理n的数位
{
s[++len]=tmp%10;
tmp/=10;
}
for (int pos=len;pos>=1;pos--)
{
for (int i=0;i<=9;i++)
{
a[i]+=f[pos-1]*s[pos];//pos位填0~s[pos]-1时pos位之后的贡献
if (i<=s[pos]-1) a[i]+=power[pos-1];//pos位填0_s[pos]-1时pos位产生的贡献
}
a[s[pos]]+=(n%power[pos-1]+1);//pos位填s[pos]时pos位产生的贡献 后面数因为有限制,所以不在此次计算贡献
a[0]-=power[pos-1];//减去这一位填0是前导0时多算的答案
}
}
signed main()
{
dp();
scanf("%lld%lld",&l,&r);
solve(r);
for (int i=0;i<=9;i++) ans[i]=a[i];
solve(l-1);
for (int i=0;i<=9;i++)
{
ans[i]-=a[i];//差分
printf("%lld ",ans[i]);
}
return 0;
}
D.数位翻转
据说很毒瘤,等我心情好了时候回来补
E.幸运666
要想办法转化成上面
专吃太强了%%%
贴 记搜版
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=12;
const int M=1e12;
int T,n;
int len,s[N];
int f[N][5];//第二位状态表示6的个数
int dfs(int pos,int c,int lim)
{
if (!pos) return c==3;
if (!lim&&f[pos][c]!=-1) return f[pos][c];
int up=9,res=0;
if (lim) up=s[pos];
for (int i=up;i>=0;i--)
{
if (c==3) res+=dfs(pos-1,3,lim&&i==s[pos]);
else if (i==6) res+=dfs(pos-1,c+1,lim&&i==s[pos]);
else res+=dfs(pos-1,0,lim&&i==s[pos]);
}
if (!lim) f[pos][c]=res;
return res;
}
int check(int x)
{
len=0,memset(f,-1,sizeof f);
while (x)
{
s[++len]=x%10;
x/=10;
}
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>>1;
if (check(mid)>=n) r=mid;
else l=mid+1;
}
printf("%lld\n",l);
}
return 0;
}
F.乘积计算
直接做做完了(现在一看记搜确实更方便捏
贴 记搜版
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=50;
const int MOD=1e7+7;
int n;
int s[N],len;
int f[N][N];//f[pos][sum]:填到pos位共有sum个1的乘积
int dfs(int pos,int sum,int lim)//lim表示第pos位填的数是否有上限
{
if (pos==0) return max(1LL*1,sum);//以防全是0
if (!lim&&f[pos][sum]!=-1) return f[pos][sum];//已搜过
int up=1,ans=1;
if (lim) up=s[pos];//上限
for (int i=0;i<=up;i++) ans=(ans*dfs(pos-1,sum+(i==1),lim&&i==s[pos]))%MOD;
if (!lim) f[pos][sum]=ans;
return ans%MOD;
}
signed main()
{
scanf("%lld",&n);
while (n)//统计n二进制的数位
{
s[++len]=n%2;
n>>=1;
}
memset(f,-1,sizeof f);
printf("%lld",dfs(len,0,1));
return 0;
}
G.奶牛编号
“辛辛苦苦”敲了二分,但因为炸裂的输出
所以这题要用组合数做。
考虑什么时候填1与0。在剩下
然后就做完了
这和数位dp有个damn的关系啊
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e3+5;
int n,k,c[N][35];
void init()
{
c[0][0]=1;
for (int i=1;i<N;i++)
{
c[i][0]=1;
for (int j=1;j<=30;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
signed main()
{
init();
scanf("%lld%lld",&n,&k);
if (n==1)
{
for (int i=1;i<=k;i++) printf("1");
return 0;
}
if (k==1)
{
printf("1");
for (int i=2;i<=n;i++) printf("0");
return 0;
}
int cnt=k;
while (c[cnt][k]<=n) cnt++;
cnt--,k--;
printf("1");
for (int i=k;i<cnt;i++) n-=c[i][k];
for (int i=1;i<=cnt;i++)
{
if (k==0) printf("0");
else if (c[cnt-i][k]>=n) printf("0");
else
{
printf("1");
n-=c[cnt-i][k],k--;
}
}
return 0;
}
H.魔法数字
记录出现的一位数可以状压解决,然后就是余数问题了
有一个小结论(感性理解永远的神):
所以可以直接记录模
于是乎可以设状态
但是这样开的空间太大,时间复杂度会爆炸。怎么办捏
我们要想办法优化一维,发现第三维可以缩。显然,判断一个数是否是
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=20;
int k,l,r;
int s[N],len;
int f[N][(1<<9)+5][512];
inline int dfs(int pos,int opt,int ys,int lim,int lst)
{
if (!pos)
{
int cnt=0;
for (int i=1;i<=9;i++)
{
if (i!=5&&(opt&(1<<(i-1)))&&ys%i==0) cnt++;
}
if (opt&(1<<4)&&(lst==5||lst==0)) cnt++;
return cnt>=k;
}
if (!lim&&f[pos][opt][ys]!=-1) return f[pos][opt][ys];
int up=9,res=0;
if (lim) up=s[pos];
for (int i=0;i<=up;i++)
{
if (i) res+=dfs(pos-1,opt|(1<<(i-1)),(ys*10+i)%504,lim&&i==up,i);
else res+=dfs(pos-1,opt,(ys*10+i)%504,lim&&i==up,i);
}
if (!lim) f[pos][opt][ys]=res;
return res;
}
inline int solve(int x)
{
len=0;
while (x)
{
s[++len]=x%10;
x/=10;
}
return dfs(len,0,0,1,0);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(f,-1,sizeof f);
cin>>k>>l>>r;
cout<<solve(r)-solve(l-1);
return 0;
}
I.异或个数
据说是道史题,那我就不做了
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!