标签:传参 保存 clu 方案 its alc 上进 分解 ping
讲解
https://blog.csdn.net/brazy/article/details/77427699
https://blog.csdn.net/wust_zzwh/article/details/52100392
数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp,字面意思就是在数位上进行dp。
数位的含义:一个数有个位、十位、百位、千位......数的每一位就是数位啦!
之所以要引入数位的概念完全就是为了dp。数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。
这些问题的特征是给定的区间特别大,不能一个个暴力的解决,必须用O(logN)的方法才行
模板
typedef long long ll;
int a[20];
ll dp[20][state];//不同题目状态不同
ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
{
//递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
//第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
/*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了
ll ans=0;
//开始计数
for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
{
if() ...
else if()...
ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
/*这里还算比较灵活,不过做几个题就觉得这里也是套路了
大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目
要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,
前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/
}
//计算完,记录状态
if(!limit && !lead) dp[pos][state]=ans;
/*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
return ans;
}
ll solve(ll x)
{
int pos=0;
while(x)//把数位都分解出来
{
a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行
x/=10;
}
return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
}
int main()
{
ll le,ri;
while(~scanf("%lld%lld",&le,&ri))
{
//初始化dp数组为-1,这里还有更加优美的优化,后面讲
printf("%lld\n",solve(ri)-solve(le-1));
}
}
单纯的不要4,两种写法,递推、记忆化搜索(基本也是这两种方法)
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//两种方法 ,递推和记忆化搜索
//单纯的不要4
//递推
int dp[20][10]; //表示第i位数,第1个数字是j时符合条件的数字数量
int a[20];
void inti(){ //初始化先
dp[0][0]=1;
for(int i=1;i<=12;i++){
for(int j=0;j<10;j++){
for(int k=0;k<10;k++){
if(j!=4)
dp[i][j]+=dp[i-1][k];
}
}
}
}
int solve1(int len){
int ans=0;
for(int i=len;i>=1;i--){ //从高位到低位处理
for(int j=0;j<a[i];j++)
if(j!=4){
ans+=dp[i][j];
if(a[i]==4) {
ans--;break;
}
}
return ans;
}
//记忆化搜索
int l,r,a[20];
int dp[20];
int dfs(int len,int ismax){
int ans=0;
int up;
if(!len) return 1;
if(!ismax&&dp[len]!=-1) return dp[len];
up=ismax? a[len]:9;
for(int i=0;i<=up;i++){
if(i==4) continue;
ans+=dfs(len-1,ismax&&i==a[len]);
}
if(!ismax) dp[len]=ans;
return ans;
}
int sovle2(int x){
int len=0;
memset(dp,-1,sizeof(dp));
while(x){
a[++len]=x%10;
x/=10;
}
return dfs(len,1)
}
int main(){
return 0;
}
1、2089 不要62
数字不能出现4和62(连续的)
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1e7+10;
const int INF=0x3fffffff;
typedef long long LL;
LL dp[maxn][2]; //后一维为0表示前面不是6,为1表示前面是6
int l,r;
int len[10];
LL dfs(int pos,int pre,int state,int limit){
if(pos==-1) return 1; //当已经到底了,就返回
if(!limit&&dp[pos][state]!=-1) return dp[pos][state];
int up=limit? len[pos]:9;
LL ans=0;
for(int i=0;i<=up;i++){ //下标从0开始
if(i==4) continue;
if(pre==6&&i==2) continue;
ans+=dfs(pos-1,i,i==6?1:0,limit&&i==len[pos]);
//state 为 i
}
//保存结果
if(!limit) dp[pos][state]=ans;
return ans;
}
LL solve(LL x){
int l=0;
while(x){
len[l++]=x%10;
x/=10;
}
return dfs(l-1,0,0,1); //后面是1
}
int main(){
while(scanf("%d %d",&l,&r)){
if(l==0&&r==0) break;
memset(dp,-1,sizeof(dp));
printf("%lld\n",solve(r)-solve(l-1));
}
return 0;
}
数字从左到右看不要有先递增后递减的情况、数位DP题的关键在于如何分析下一个数字的情况
//这道题要考虑前导0的影响
//而且对数字前后的要求
int t;
char s[110];
int mod= 1000000007;
int a[110];
LL dp[110][10][3]; //分别表示位数、前面的数pre,倾向turn
LL dfs(int pos,int pre,int turn,bool limit,bool inv){ ///turn:0不清楚,1下降,2上升
//这条语句中pos是要DP的位置,pre,turn,limit,invalid...这些都是前提条件,或者
//说是之前位置上确定一些数之后的状态,而这个dfs要进行的就是在这个状态下继续
//确定下一位的数字
if(pos==-1) return inv? 0:1;
if(!limit&&dp[pos][pre][turn]!=-1) return dp[pos][pre][turn];
int up=limit? a[pos]:9;
LL ans=0LL; //注意要加LL
for(int i=0;i<=up;i++){
if(turn==2&&i<pre) continue; //不能先上升在下降
int p=0;
if(i==pre) p=turn;
else if(i<pre) p=1;
else p=2;
if(inv) p=0; //随时控制前导0
ans+=dfs(pos-1,i,p,limit&&i==a[pos],inv&&i==0);
ans%=mod;
}
ans%=mod;
if(!limit) dp[pos][pre][turn]=ans;
return ans;
}
int main(){
cin>>t;
while(t--){
scanf("%s",s);
memset(dp,-1,sizeof(dp));
int len=strlen(s);
for(int i=0;i<len;i++) a[i]=s[len-i-1]-‘0‘;
printf("%lld\n",dfs(len-1,0,0,1,1));
}
return 0;
}
3、HDU 4734
题目给了个f(x)的定义:F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1,Ai是十进制数位,然后给出a,b求区间[0,b]内满足f(i)<=f(a)的i的个数。
常规想:这个f(x)计算就和数位计算是一样的,就是加了权值,所以dp[pos][sum],这状态是基本的。a是题目给定的,f(a)是变化的不过f(a)最大好像是4600的样子。如果要memset优化就要加一维存f(a)的不同取值,那就是dp[10][4600][4600],这显然不合法。
这个时候就要用减法了,dp[pos][sum],sum不是存当前枚举的数的前缀和(加权的),而是枚举到当前pos位,后面还需要凑sum的权值和的个数,
也就是说初始的是时候sum是f(a),枚举一位就减去这一位在计算f(i)的权值,那么最后枚举完所有位 sum>=0时就是满足的,后面的位数凑足sum位就可以了。
仔细想想这个状态是与f(a)无关的(新手似乎很难理解),一个状态只有在sum>=0时才满足,如果我们按常规的思想求f(i)的话,那么最后sum>=f(a)才是满足的条件。
但是在函数调用的时候
intdfs(int pos,int sum,bool limit) 里面的sum表示的还是当前已经有的数
//减法
int f(int x){
if(x==0) return 0;
int ans=f(x/10);
return ans*2+(x%10);
}
int dp[10][5000];
int a[12];
int all;
int dfs(int pos,int sum,int limit){
if(pos==-1) return sum<=all; //注意这里
if(sum>all) return 0; //如果大的话,就直接返回
if(!limit&&dp[pos][all-sum]!=-1) return dp[pos][all-sum]; //减法
int up=limit? a[pos]:9;
int ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos-1,sum+i*(1<<pos),limit&&i==a[pos]); //计算
}
if(!limit ) dp[pos][all-sum]=ans;
return ans;
}
int sovle(int x){
int len=0;
while(x){
a[len++]=x%10;
x/=10;
}
return dfs(len-1,0,1);
}
int main(){
int t,op=1;
int a,b;
scanf("%d",&t);
memset(dp,-1,sizeof(dp));
while(t--){
scanf("%d %d",&a,&b);
all=f(a);
//sovle(b);
printf("Case #%d: %d\n",op++,sovle(b));
}
return 0;
}
4、POJ 3252
这题的约束就是一个数的二进制中0的数量要不能少于1的数量,通过上一题,这题状态就很简单了,dp[pos][num],到当前数位pos,0的数量减去1的数量不少于num的方案数,一个简单的问题,中间某个pos位上num可能为负数(这不一定是非法的,因为我还没枚举完嘛,只要最终的num>=0才能判合法,中途某个pos就不一定了),这里比较好处理,Hash嘛,最小就-32吧(好像),直接加上32,把32当0用。这题主要是要想讲一下lead的用法,显然我要统计0的数量,前导零是有影响的。至于!lead&&!limit才能dp,都是类似的,自己慢慢体会吧
//要考虑前导零的影响,因为题目的要求
//dp[pos][num],到当前数位pos,0的数量减去1的数量不少于num的方案数,一个简单的问题,
//中间某个pos位上num可能为负数(这不一定是非法的,因为我还没枚举完嘛,只要最终的num>=0才能判合法,
//中途某个pos就不一定了),这里比较好处理,Hash嘛,最小就-32吧(好像),直接加上32,把32当0用。这题主要是要想讲一下lead的用法,
//显然我要统计0的数量,前导零是有影响的。!lead&&!limit才能dp
int dp[35][100];
int a[60];
int dfs(int pos,int sta,bool lead,bool limit){ //位数、0-1的数,前导零,上限
if(pos==-1) return sta>=32;
if(!lead&&!limit&&dp[pos][sta]!=-1) return dp[pos][sta];
int up=limit? a[pos]:1; //上限
int ans=0;
for(int i=0;i<=up;i++){
if(lead&&i==0) //如果有前导零,就略过
ans+=dfs(pos-1,sta,lead,limit&&i==a[pos]);
else ans+=dfs(pos-1,sta+(i==0? 1:-1),lead&&i==0,limit&&i==a[pos]);
}
if(!limit&&!lead) dp[pos][sta]=ans;
return ans;
}
int solve(LL x){
int len=0;
while(x){
a[len++]=x&1;
x>>=1;
}
return dfs(len-1,32,1,1); //以32为起点,以免中间有负数
}
int main(){
memset(dp,-1,sizeof(dp));
LL a,b;
scanf("%lld %lld",&a,&b);
printf("%d\n",solve(b)-solve(a-1));
return 0;
}
一本通

跟上面的减法一样的思想,但是这个也有树的思想
论文和题解:
https://wenku.baidu.com/view/d2414ffe04a1b0717fd5dda8.html
统计区间[0,x]内二进制表示含k个1的数的个数 。统计一棵高度为 i 的完全二叉树内二进制表示中恰好含有 j 个 1的数的个数
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//这道题感觉理解还是不是很彻底
//https://blog.csdn.net/primoblog/article/details/13168287?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-3
int dp[32][32],a[32];
int k,b,x,y;
void inti(){
dp[0][0]=1;
for(int i=1;i<=31;i++){
dp[i][0]=dp[i-1][0]; //有题解提到用完全二叉树理解
for(int j=1;j<=i;j++)
dp[i][j]=dp[i-1][j]+dp[i-1][j-1];
}
}
int calc(int xx){ ////统计区间[0,x]内二进制表示含k个1的数的个数
//统计一棵高度为 i 的完全二叉树内二进制表示中恰好含有 j 个 1的数的个数
int tot=0,ans=0,len=0;//tot记录当前路径上已有的1的数量,ans为答案
while(xx){
a[++len]=xx%b;
xx/=b;
}
for(int i=len;i>0;i--){
if(a[i]==1){
ans+=dp[i-1][k-(tot++)];
if(tot==k) break;
}
else if(a[i]>1){
ans+=dp[i][k-tot];
break;
}
}
return tot==k?ans+1:ans;
}
int main(){
scanf("%d %d %d %d",&x,&y,&k,&b);
inti(); //初始化
printf("%d\n",calc(y)-calc(x-1));
return 0;
}
指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。
int f[32][32]/f[位数i][第i位(最高位)的数字] 的合理情况
递推的做法:
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
int f[32][32],a[32];
// //f[位数i][第i位(最高位)的数字]
void inti(){ //初始化
for(int i=1;i<=31;i++) f[1][i]=1;
for(int i=2;i<=31;i++){
for(int j=0;j<=9;j++){
for(int k=j;k<=9;k++){
f[i][j]+=f[i-1][k];
}
}
}
}
int solve(int x){
int len=0;
memset(a,0,sizeof(a));
while(x){
a[++len]=x%10;
x/=10;
}
int ans=0; //方案数
for(int i=len;i;i--){
if(a[i+1]>a[i]) break; //不降数,下降了
for(int j=a[i+1];j<a[i];j++){ //不降数,必须比前一个大,比现在这个小
ans+=f[i][j];
}
if(i==1) ans++; //本身也是 (一位数)
}
return ans;
}
int main(){
int x,y;
inti();
while(scanf("%d %d",&x,&y)!=EOF){
printf("%d\n",solve(y)-solve(x-1));
}
return 0;
}
记忆化搜索的做法:
LL dp[20][20][2]; 分别表示位第i位,填的数字为j,是否是上界,是否是前导0
#include<bits/stdc++.h>
using namespace std;
const int maxn = 12;
int n,l,r,f[maxn][maxn][2];
char buf[maxn];
int dfs(int pos,int pre,int limit){
if(pos==n) return 1;
if(f[pos][pre][limit]) return f[pos][pre][limit];
int mx=limit?buf[pos]-‘0‘:9,ans=0;
for( int i=0; i<=mx; i++ ) if(i>=pre) ans+=dfs(pos+1,i,limit&(i==mx));
return f[pos][pre][limit]=ans;
}
int solve(int num){
memset(f,0,sizeof(f));
sprintf(buf,"%d",num),n=strlen(buf);
return dfs(0,0,1);
}
int main()
{
while(cin>>l>>r) cout<<solve(r)-solve(l-1)<<‘\n‘;
return 0;
}
Windy 定义了一种 Windy 数:不含前导零且相邻两个数字之差至少为2的正整数被称为 Windy 数。
递推的做法(感觉要好理解一点)TAT
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
//看了一下,觉得这道题用递推更好理解
int f[32][32],a[32];
// //f[位数i][第i位(最高位)的数字]
void inti(){ //递推都需要初始化
for(int i=0;i<=9;i++) f[1][i]=1; //只有1位
for(int i=2;i<=31;i++){
for(int j=0;j<=9;j++){
for(int k=0;k<=9;k++){
if(abs(j-k)>=2) f[i][j]+=f[i-1][k];
}
}
}
}
int solve(int x){
memset(a,0,sizeof(a));
int len=0;
while(x){
a[++len]=x%10;
// cout<<a[len]<<" ";
x/=10;
}
// cout<<endl;
//按照顺序处理位数
int ans=0;
for(int i=1;i<len;i++){ //不足len的位的部分
for(int j=1;j<=9;j++) ans+=f[i][j];
//不能从0开始
}
for(int i=1;i<a[len];i++) //第len位不足a[len]的部分
ans+=f[len][i];
//然后处理第len位为a[len]的数据,因为是很大的树,所以要控制上限
for(int i=len-1;i;i--){
for(int j=0;j<a[i];j++){ //最高位已经确定了,所以可以取到0了
if(abs(j-a[i+1])>=2) ans+=f[i][j]; ////跟前一位比较
}
if(abs(a[i+1]-a[i])<2) break;
if(i==1) ans++; //?因为上面处理 不足len的位的部分 的时候没有处理1位
}
return ans;
}
int main(){
inti();
int x,y;
cin>>x>>y;
cout<<solve(y)-solve(x-1)<<endl;
return 0;
}
记忆化搜索 四维 dp[i][j][Bo1][Bo2]第i位,填的数字为j,是否是上界,是否是前导0
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
int a,b,len;
int dp[30][30][2][2];
int num[30];
//https://www.cnblogs.com/gaojunonly1/p/10360015.html
//https://blog.csdn.net/qq_42367531/article/details/82526133
//windy数,也是很友好的数位dp,也像数字游戏一样搞一搞,dp[i][j][Bo1][Bo2]第i位,填的数字为j,是否是上界,是否是前导0就over了
int dfs(int pos,int pre,int limit,int inv){ //位数,前面的数,上界,前导零
if(pos==1) return dp[pos][pre][limit][inv]=1;
if(dp[pos][pre][limit][inv]) return dp[pos][pre][limit][inv];
int up=limit? num[pos-1]:9;
//int ans=0;
for(int i=0;i<=up;i++){
if(inv||abs(pre-i)>1){
int b1=(limit&&i==up); //判断方式!!
int b2=(inv&&i==0);
dp[pos][pre][limit][inv]+=dfs(pos-1,i,b1,b2);
}
}
return dp[pos][pre][limit][inv];
}
int solve(int n){
if(n==0) return 1;
memset(dp,0,sizeof(dp));
len=0;
while(n){
num[++len]=n%10;
n/=10;
}
int ans=0;
ans+=dfs(len,0,0,1); //
for(int i=1;i<num[len];i++){
ans+=dfs(len,i,0,0);
}
ans+=dfs(len,num[len],1,0);
return ans;
}
/*
//只用二维
////f[第i高位][填的数字]
int f[10][10];
int dfs(int pos,int pre,int inv,int limit){
if(pos==0) return 1;
if(limit==0&&dp[pos][inv]!=-1) return dp[pos][inv];
int ans=0,up;
up=limit? a[pos]:9;
for(int i=0;i<=up;i++){
if(limit==1){ //有前导零
int zz=(i==0)?1:0; //inv的设置也为zz
if(limit==1&&i==up) ans+=dfs(pos-1,i,zz,1); //上界限制为1
else ans+=dfs(pos-1,i,zz,0); //上界限制为0
}
else if(abs(pre-i)>=2){
if(limit&&i==up) ans+=dfs(pos-1,i,0,1);
//前导零为0,但是上界限制为1
else ans+=dfs(pos-1,i,0,0);
//上界限制也为0
}
}
if(!limit&&!inv) f[len][pre]=ans;
return ans;
}
//调用的时候
return dfs(len,0,1,1);
*/
int main(){
scanf("%d %d",&a,&b);
printf("%d",solve(b)-solve(a-1));
return 0;
}
某人又命名了一种取模数,这种数字必须满足各位数字之和mod N=0 。现在大家又要玩游戏了,指定一个整数闭区间[a,b],问这个区间内有多少个取模数。
这道题还比较简单,但是要注意函数写法,返回的是summ==0; //返回一个判断
ans+=dfs(pos+1,(summ+i)%k,limit&&i==up);
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
int l,r,k;
int dp[40][110];
int num[40];
int len;
int dfs(int pos,int summ,int limit){
if(pos>len) return summ==0; //返回一个判断
if(dp[pos][summ]!=-1&&!limit) return dp[pos][summ];
int up=limit?num[len-pos+1]:9;
int ans=0;
for(int i=0;i<=up;i++){
ans+=dfs(pos+1,(summ+i)%k,limit&&i==up);
}
if(!limit) dp[pos][summ]=ans;
return ans;
}
int solve(int x){
memset(num,0,sizeof(num));
memset(dp,-1,sizeof(dp));
len=0;
while(x){
num[++len]=x%10;
x/=10;
}
return dfs(1,0,1);
}
int main(){
while(~scanf("%d %d %d",&l,&r,&k)){
printf("%d\n",solve(r)-solve(l-1));
}
return 0;
}
好难TAT https://blog.csdn.net/deerly_/article/details/79930085
需要维护三个值 --->定义结构体,假定dfs推出返回的结构体是tmp,当前结果的结构体是ans
1.符合条件数的个数 cnt
2.符合条件数的和 sum
3.符合条件数的平方和 sqr
三个条件
(1)数中某一位是 7; 基础的数位dp很好维护;
(2)整数的每一位加起来的和是7 的整数倍;
tmp.sum * 10 + (10 ^ pos * i) * ans.cnt 就是上一步状态的和加上这一步加的
这一步加的就是10的当前位次方乘以i,因为有ans.cnt个嘛,所以再乘以ans.cnt
(3)这个整数是 7 的整数倍。
3 首先重新构建一下这个数 (10^pos * i + x)x是这个数的后面部分,就是上一次状态得到的那个数,则平方和就是(10^len*i)^2+x^2+2*10^len*i*x, 其中x^2=tmp.sqr;
ans.sqr += (2*10^pos*i*x)*tmp.cnt=(2*10^pos*i)*next.sum(神奇的化简)
ans.sqr += (10^pos*i)^2*tmp.cnt;
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
const long long MOD = 1e9 + 7;
typedef long long LL;
//好难啊
/*
整数中某一位是 77;
整数的每一位加起来的和是 77 的整数倍;
这个整数是 77 的整数倍。
首先这三个条件都是基础数位dp,就不说了。
重点是怎么去求平方和。
需要维护三个值
1.符合条件数的个数 cnt
2.符合条件数的和 sum
3.符合条件数的平方和 sqr
为什么要维护这三个呢,接着往下看你就知道了
假定dfs推出返回的结构体是tmp,当前结果的结构体是ans
其中1是基础的数位dp很好维护;
2 tmp.sum * 10 + (10 ^ pos * i) * ans.cnt 就是上一步状态的和加上这一步加的
这一步加的就是10的当前位次方乘以i,因为有ans.cnt个嘛,所以再乘以ans.cnt
3 首先重新构建一下这个数 (10^pos * i + x)x是这个数的后面部分,就是上一次状态得到的那个数,则平方和就是(10^len*i)^2+x^2+2*10^len*i*x, 其中x^2=tmp.sqr;
ans.sqr += (2*10^pos*i*x)*tmp.cnt=(2*10^pos*i)*next.sum(神奇的化简)
ans.sqr += (10^pos*i)^2*tmp.cnt;
原文链接:https://blog.csdn.net/deerly_/article/details/79930085
*/
LL p[25]; //这个是位数,1 10 100 1000 10000这种
LL num[40];
struct node{ //要用到结构体
LL cnt,summ,sqr;
//符合条件数的个数 符合条件数的和 符合条件数的平方和
node(){cnt=-1,summ=sqr=0;}
node(LL cnt,LL summ,LL sqr) : cnt(cnt),summ(summ),sqr(sqr){}
}dp[20][20][20];
LL t,l,r,len;
node dfs(int pos,int sum1,int sum2,bool limit){ //sum1为每一位加起来的和,sum2
if(pos==0){
if(sum1&&sum2){
return node(1,0,0); //个数为1
}
return node(0,0,0);
}
if(!limit&&dp[pos][sum1][sum2].cnt!=-1) return dp[pos][sum1][sum2];
int up=limit? num[pos]:9;
node ans;
ans.cnt=0;
for(int i=0;i<=up;i++){
if(i==7) continue;
node temp=dfs(pos-1,(i+sum1)%7,(sum2*10+i)%7,limit&&i==up);
ans.cnt+=temp.cnt; //这个可以直接加,很好维护
ans.cnt%=MOD;
ans.summ+=(temp.summ+((p[pos]*i)%MOD)*temp.cnt%MOD)%MOD;
ans.summ%MOD;
//看看这个怎么计算的
ans.sqr+=(temp.sqr+((2*p[pos]*i)%MOD)*temp.summ)%MOD;
ans.sqr%=MOD;
ans.sqr+=((temp.cnt*p[pos])%MOD*p[pos]%MOD*i*i%MOD);
ans.sqr%=MOD;
}
if(!limit) dp[pos][sum1][sum2]=ans;
return ans;
}
LL solve(LL n){
len=0;
while(n){
num[++len]=n%10;
n/=10;
}
node v=dfs(len,0,0,1);
return v.sqr;
}
int main(){
scanf("%d",&t);
p[1]=1;
for(int i=2;i<=20;i++) p[i]=(p[i-1]*10)%MOD;
while(t--){
scanf("%lld %lld",&l,&r);
LL ans=solve(r);
ans-=solve(l-1);
printf("%lld\n",(ans%MOD+MOD)%MOD);
}
return 0;
}
给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码digit 各出现了多少次。
10次dfs
#include<iostream>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<stack>
#include<cstdio>
#include<queue>
#include<map>
#include<vector>
#include<set>
using namespace std;
const int maxn=1010;
const int INF=0x3fffffff;
typedef long long LL;
LL l,r;
LL len;
LL dp[20][110];
LL num[20];
LL dfs(int pos,int dig,LL summ,int lead,int limit){
//位数 上一个数字 方案数 前导0 上界
if(pos>len) return summ;
if(dp[pos][summ]!=-1&&!limit&&!lead) return dp[pos][summ];
int up=limit? num[len-pos+1]:9; //是递增的
LL ans=0;
for(int i=0;i<=up;i++){
if(lead&&i==0) //有前导零
ans+=dfs(pos+1,dig,0,1,up==i&&limit); //方案数位0
else
ans+=dfs(pos+1,dig,summ+(i==dig?1:0),0,i==up&&limit); //是对应的数字的话,那么方案数+1
}
if(!limit&&!lead) dp[pos][summ]=ans;
return ans;
}
LL work(LL x,int dig){
memset(dp,-1,sizeof(dp));
len=0;
while(x){
num[++len]=x%10;
x/=10;
}
return dfs(1,dig,0,1,1);
}
int main(){
scanf("%lld %lld",&l,&r);
if(l){
for(int i=0;i<=9;i++){
printf("%lld ",work(r,i)-work(l-1,i));
}
}
else{
for(int i=0;i<=9;i++){
printf("%lld ",work(r,i)-work(l,i));
}
}
return 0;
}
#include<iostream>#include<cstring>#include<cmath>#include<algorithm>#include<stack>#include<cstdio>#include<queue>#include<map>#include<vector>#include<set>using namespace std;const int maxn=1e7+10;const int INF=0x3fffffff;typedef long long LL;LL dp[maxn][2]; //后一维为0表示前面不是6,为1表示前面是6int l,r;int len[10];LL dfs(int pos,int pre,int state,int limit){ if(pos==-1) return 1; //当已经到底了,就返回if(!limit&&dp[pos][state]!=-1) return dp[pos][state];int up=limit? len[pos]:9;LL ans=0;for(int i=0;i<=up;i++){ //下标从0开始 if(i==4) continue;if(pre==6&&i==2) continue;ans+=dfs(pos-1,i,i==6?1:0,limit&&i==len[pos]);//state 为 i } //保存结果 if(!limit) dp[pos][state]=ans;return ans; } LL solve(LL x){int l=0;while(x){len[l++]=x%10;x/=10;}return dfs(l-1,0,0,1); //后面是1 }int main(){while(scanf("%d %d",&l,&r)){if(l==0&&r==0) break;memset(dp,-1,sizeof(dp));printf("%lld\n",solve(r)-solve(l-1));}return 0;}
标签:传参 保存 clu 方案 its alc 上进 分解 ping
原文地址:https://www.cnblogs.com/shirlybaby/p/12318799.html