「一本通」数位dp学习笔记

8 篇文章 0 订阅
6 篇文章 1 订阅
废话:

从数位dp只会windy数变成什么都不会 从只会写递推变成只会写记忆化搜索…

总结:

大概就是解决一些对数位有要求而且上下限特别的大…一般有两种实现方法,递推(dp,比较好理解一般都是先学这一种)/记忆化搜索(暴力,方便,容易写,sb方法)

板子:

https://www.luogu.org/blog/mak2333/shuwei


loj#10163. 「一本通 5.3 例 1」Amount of Degrees

https://loj.ac/problem/10163

几个次幂相加,等于转到b进制时那一位为1。转成多叉树的形式对每一棵含有答案的子树乱搞。注意对应的次幂
分析&论文:https://wenku.baidu.com/view/d2414ffe04a1b0717fd5dda8.html

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[32][32],a[32];//f:有i层(有i位)有j个1的个数 (整数次幂相加 对应的b进制数位上应为1)
int k,b;
int solve(int x)
{
	int len=0,kk=k;
	while (x!=0)
	{
		a[len++]=x%b; //注意对应次幂 
		x/=b;
	}
	int ans=0; len--;
	for (int i=len;i>=0;i--)
	{
		if (a[i]==1)
		{
			ans+=f[i][kk];
			kk--;
		}
		else if (a[i]>1)
		{
			ans+=f[i+1][kk];
			break;
		}
		if (kk<0) return ans;
	}
	if (kk==0) ans++; //x刚好也是k个1
	return ans;
}
int main()
{
	for (int i=0;i<31;i++)
	{
		f[i][0]=1;
		for (int j=1;j<=i;j++) f[i][j]=f[i-1][j]+f[i-1][j-1];
	}
	int x,y;
	scanf("%d%d%d%d",&x,&y,&k,&b);
	printf("%d\n",solve(y)-solve(x-1));
	return 0;
}

loj#10164. 「一本通 5.3 例 2」数字游戏

https://loj.ac/problem/10164

递推做法:
//递推 
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int a[32],f[32][32];//f[i位][开始的数字](递推用开始的数  记忆化搜索用最后一位数?)
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+1-1][j];
		}
		if (i==1) ans++;//本身也是 
	}
	return ans;
}
int main()
{
	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 x,y;
	while (scanf("%d%d",&x,&y)!=EOF)
	{
		printf("%d\n",solve(y)-solve(x-1));
	}
	return 0;
} 
记忆化搜索:
//记忆化搜索
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[32][32],a[32];//f[第i高位][填的数字]   
int dfs(int len,int st,int lim)
{
	if (len==0) return 1;
	if (lim==0&&f[len][st]!=-1) return f[len][st];
	int ans=0,ed; if (lim==1) ed=a[len]; else ed=9;
	for (int i=st;i<=ed;i++)
	{
		if (lim==1&&i==ed) ans+=dfs(len-1,i,1);
		else ans+=dfs(len-1,i,0);
	}
	return ans;
}
int solve(int x)
{
	memset(f,-1,sizeof(f));
	int len=0;
	while (x)
	{
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,0,1);
}
int main()
{
	int x,y;
	while (scanf("%d%d",&x,&y)!=EOF)
	{
		printf("%d\n",solve(y)-solve(x-1));
	}
	return 0;
} 

loj#10165. 「一本通 5.3 例 3」Windy 数

https://loj.ac/problem/10165

递推做法:
//递推
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
int f[32][32],a[32]; //f[位数i][第i位(最高位)的数字] 
void init()
{
	for (int i=0;i<=9;i++) f[1][i]=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;
		x/=10;
	}
	int ans=0;
	for (int i=1;i<len;i++)//不足len位的部分 
		for (int j=1;j<=9;j++) 
			ans+=f[i][j];
	for (int i=1;i<a[len];i++) //第len位不足a[len]的部分
	{
		ans+=f[len][i];
	}
	for (int i=len-1;i;i--) 
	{
		for (int j=0;j<a[i];j++) if (abs(j-a[i+1])>=2) ans+=f[i][j]; //跟前一位比较 
		if (abs(a[i+1]-a[i])<2) break;
		if (i==1) ans++;
	}
	return ans;
}
int main()
{
	init();
	int a,b;
	scanf("%d%d",&a,&b);
	printf("%d\n",solve(b)-solve(a-1));
	return 0;
}
记忆化搜索做法:
//记忆化搜索
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[10][10],a[10];//f[第i高位][填的数字]
int dfs(int len,int st,int ze,int lim)//ze是否有前导零  lim:是否达到上限 
{
	if (len==0) return 1;
	if (lim==0&&f[len][st]!=-1) return f[len][st];
	int ans=0,ed; 
	if (lim==1) ed=a[len]; else ed=9; 
	for (int i=0;i<=ed;i++) 
	{ 
		if (ze==1)
		{
			int zz=0;
			if (i==0) zz=1; 
			if (lim==1&&i==ed) ans+=dfs(len-1,i,zz,1);
			else ans+=dfs(len-1,i,zz,0);
		}
		else if (abs(st-i)>=2)
		{
			if (lim==1&&i==ed) ans+=dfs(len-1,i,0,1);
			else ans+=dfs(len-1,i,0,0);
		}
	}
	if (lim==0&&st!=0) f[len][st]=ans;
	return ans;
}
int solve(int x)
{
	memset(a,0,sizeof(a));
	int len=0;
	while (x)
	{
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,0,1,1);
}
int main()
{
	memset(f,-1,sizeof(f));
	int a,b;
	scanf("%d%d",&a,&b);
	printf("%d\n",solve(b)-solve(a-1));
	return 0;
}

loj#10166. 「一本通 5.3 练习 1」数字游戏

https://loj.ac/problem/10166
不要想太多,直接爆搜就好

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[32][110],a[32],n;//f[位数][余数]
int dfs(int len,int mod,int lim)
{
	if (len==0) 
	{
		if (mod==0) return 1; else return 0;
	}
	if (f[len][mod]!=-1&&lim==0) return f[len][mod];
	int ans=0,ed;
	if (lim==1) ed=a[len]; else ed=9;
	for (int i=0;i<=ed;i++)
	{
		if (lim==1&&i==ed) ans+=dfs(len-1,(mod+i)%n,1);
		else ans+=dfs(len-1,(mod+i)%n,0);
	}
	if (lim==0) f[len][mod]=ans;
	return ans;
}
int solve(int x)
{
	memset(f,-1,sizeof(f));
	memset(a,0,sizeof(a));
	int len=0;
	while (x)
	{
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,0,1);
}
int main()
{
	int a,b;
	while (scanf("%d%d%d",&a,&b,&n)!=EOF)
		printf("%d\n",solve(b)-solve(a-1));
	return 0;
}

loj#10167. 「一本通 5.3 练习 2」不要 62

https://loj.ac/problem/10167

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int f[7][10],a[7];//f[位数i][第i位数字]
int dfs(int len,int last,int lim)
{
	if (len==0) return 1;
	if (lim==0&&f[len][last]!=-1) return f[len][last];
	int ans=0,ed;
	if (lim==1) ed=a[len]; else ed=9;
	for (int i=0;i<=ed;i++)
	{
		if (i==4) continue;
		if (last==6&&i==2) continue;
		if (lim==1&&i==ed) ans+=dfs(len-1,i,1);
		else ans+=dfs(len-1,i,0);
	}
	if (lim==0) f[len][last]=ans;
	return ans;
}
int solve(int x)
{
	memset(f,-1,sizeof(f));
	memset(a,0,sizeof(a));
	int len=0;
	while (x)
	{
		a[++len]=x%10;
		x/=10;
	}
	return dfs(len,0,1);
}
int main()
{
	int a,b;
	while (scanf("%d%d",&a,&b)!=EOF&&a&&b)
	{
		printf("%d\n",solve(b)-solve(a-1));
	}
	return 0;
}

loj#10168. 「一本通 5.3 练习 3」恨 7 不成妻

https://loj.ac/problem/10168
完全平方公式2333(没想到 装傻.jpg)
题解&分析:https://blog.csdn.net/ten_three/article/details/19698055
&https://www.cnblogs.com/kuangbin/archive/2013/05/01/3053233.html

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define LL long long
#define mod 1000000007
struct node
{
	LL s,sum,sqrsum;//个数N 各数之和 各数平方和 
	node()
	{
		s=sum=sqrsum=-1;
	}
}f[20][10][10];//f[位数][各数位之和mod7][这个数mod7] 
int a[20];
LL pi[20];
node dfs(int len,int s,int qs,int lim)//数位 各数位之和mod7 这个数mod7 是否上限 
{
	if (len==0)
	{
		node tt;
		tt.s=tt.sum=tt.sqrsum=0;
		if (s!=0&&qs!=0) tt.s=1;
		return tt;
	}
	if (lim==0&&f[len][s][qs].sqrsum!=-1) return f[len][s][qs];
	node ans; ans.s=ans.sum=ans.sqrsum=0;
	int ed=0;
	if (lim==1) ed=a[len]; else ed=9;
	for (int i=0;i<=ed;i++)
	{
		if (i==7) continue;
		/*
		第len位填i f1,f2,f2...fN记录前len位已经填好的数字
		对答案的贡献为:  (f1+ i*10^(len-1) )^2+(f2+ i*10^(len-1) )^2+...+(fN+ i*10^(len-1) )^2
		将平方和展开 整理 提公因式:
		N*(i*10^(len-1))^2+ f1^2+f2^2+...+fN^2+ 2*(i*10^(len-1))*(f1+f2+...fN) 
		其中 N即为要求的s   2*(i*10^(len-1))*(f1+f2+...fN) 即为要求的sum   整一个即为sqrsum 
		*/
		node tt;
		if (lim==1&&i==ed) tt=dfs(len-1,(s+i)%7,(qs*10+i)%7,1);
		else tt=dfs(len-1,(s+i)%7,(qs*10+i)%7,0);
		LL si=(i*pi[len-1])%mod;
		ans.s=(ans.s+tt.s)%mod; 
		ans.sum=(ans.sum+(tt.sum+ (si*tt.s)%mod ) %mod )%mod;
		ans.sqrsum=(ans.sqrsum+tt.sqrsum+( ((si*si)%mod*tt.s)%mod+ ((2*si)%mod *tt.sum) %mod )%mod )%mod;
	}
	if (lim==0) f[len][s][qs]=ans;
	return ans;
}
LL solve(LL x)
{
	memset(a,0,sizeof(a));
	int len=0;
	while (x)
	{
		a[++len]=x%10;
		x/=10;
	}
	node ans=dfs(len,0,0,1);
	return ans.sqrsum%mod;
}
int main()
{
	pi[0]=1;
	for (int i=1;i<=18;i++) pi[i]=pi[i-1]*10;
	int t;
	scanf("%d",&t);
	while (t--)
	{
		LL x,y;
		scanf("%lld%lld",&x,&y);
		if (x>y) swap(x,y);
		printf("%lld\n",(solve(y)-solve(x-1)+mod)%mod);
	}
	return 0;
}

loj#10169. 「一本通 5.3 练习 4」数字计数

https://loj.ac/problem/10169
别人都是三维f[数位][ ][前导零]+做10次,每次找一个数
只有我是f[数位][填的数字]鬼畜结构体一次找完所有数字且不带前导零……
所以注意前导零!!!具体看代码里解释的!
不加前导零那一维就要记得预处理

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
struct node
{
	bool bk;
	long long a[10];
	node()
	{
		bk=0; memset(a,-1,sizeof(a));
	}
}f[15][10];
int a[15];
long long pi[15],c[15];
node jia(node x,node y)
{
	for (int i=0;i<=9;i++) x.a[i]+=y.a[i];
	return x;
}
node dfs(int len,int x,int ze,int lim) //ze:zero是否有前导零
{
	if (len==0) {node tt;tt.bk=1;memset(tt.a,0,sizeof(tt.a));tt.a[x]=1;return tt;}
	if (ze==0&&lim==0&&f[len][x].bk==1) return f[len][x];
	node ans; ans.bk=1; memset(ans.a,0,sizeof(ans.a));
	int ed;
	if (lim==1) ed=a[len]; else ed=9;
	for (int i=0;i<=ed;i++)
	{
		node tt;
		tt=dfs(len-1,i,i==0&&ze==1,i==ed&&lim==1);
		/*if (ze==1&&i==0) ;
		else if (len>1) tt.a[i]+=tt.t;*/
		if (ze==1&&i==0) ;
		else
		{
			if (len>1) 
			{
				if (lim==1&&i==ed) tt.a[i]+=c[len-1]+1;
				else tt.a[i]+=pi[len-1];
			}
		}
		ans=jia(ans,tt);
	}
	if (ze==0&&lim==0) f[len][x]=ans;
	//关于前导零不能用/存答案 例1010 当len=3,往下找len=2的情况时 由于此时f[2][ ].a[0]=9 (10,20,30,...,90) 而答案应为18 (10,20,...,90; 01,02,...,09) 
	return ans;
}
node solve(long long x)
{
	memset(a,0,sizeof(a));
	memset(c,0,sizeof(c));
	/*for (int i=0;i<15;i++)
		for (int j=0;j<10;j++) memset(f[i][j].a,-1,sizeof(f[i][j].a)),f[i][j].bk=0;*/
	int len=0; 
	while (x)
	{
		a[++len]=x%10;
		x/=10;
		c[len]=a[len]*pi[len-1]+c[len-1]; //预处理最多有多少个数在这里面(当前枚举位出现次数)
	}
	return dfs(len,0,1,1);
}
int main()
{
	pi[0]=1;
	for (int i=1;i<15;i++) pi[i]=pi[i-1]*10;
 	long long x,y;
	scanf("%lld%lld",&x,&y);
	node r=solve(y),l=solve(x-1); 
	for (int i=0;i<=9;i++)
	{
		printf("%lld ",r.a[i]-l.a[i]);
	}
	printf("\n");
	return 0;
}

完结撒花!
(sd记忆化搜索真好用)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值