【总结】数位dp

个人认为是比较简单的 d p dp dp

首先,状态不难想,可求状态有这些:位数,前导零,limit标记,余数,满足条件的数的和,满足条件的数的平方和等。

其次,注意编程的细节。然后这道题就搞定了。

数位 d p dp dp的时间复杂度很好分析,就是所有可能的状态数。一般状态数 s s s<=1e5时不容易超时,如果状态数小,不考虑常数的话,可以将 i n t int int宏定义成 l o n g l o n g long long longlong

若数值大于 l o n g l o n g longlong longlong,则用字符串输入,其他操作与正常的数相同
结果一般要取模(因为数太多)

CQOI2016手机号码 为例:
这里用了一个小技巧。实际上前导零在判断数是否合法时会产生错误答案,但是由于 [ l , r ] [l,r] [l,r]不含前导零,而 [ 1 , l − 1 ] [1,l-1] [1,l1]又都被以同种规则算了一次,所以会抵消。

但是当 l = 1 0 10 l=10^{10} l=1010时, l − 1 l-1 l1是10位数,而 r r r 11 11 11位数,所以判断条件其实是不等价的。我们只需在第 11 11 11位补一个 0 0 0即可,这样和 r r r算出来的重叠部分就可以抵消了。

#include<bits/stdc++.h>
#define int long long
//This is made by something 
//O(状态数)
int n,m,F[20][10][3][2][2][2];
int a[20],len;
int dfs(int x,int pre,int num,int f,int e,int d,int limit) {
	//强行改状态 
	if(num>=3) d=1;
	if(d==1) pre=num=0;
	if(f==1&&e==1) {
		return 0;
	}
	if(x==0) {
		return d;
	}
	if(!limit&&F[x][pre][num][f][e][d]!=-1) return F[x][pre][num][f][e][d];
	int maxn= (limit==1?a[x]:9),ans=0;
	for(int i=0;i<=maxn;i++) {
		ans+=dfs(x-1,i,(i==pre?num+1:1),f||(i==4),e||(i==8),d,limit&&i==a[x]);
	}
	if(!limit) F[x][pre][num][f][e][d]=ans;
	return ans;
}
int solve(int x) {
	len=0; while(x) a[++len]=x%10,x/=10;
	if(len!=11) a[++len]=0;
	return dfs(len,-1,0,0,0,0,1);
}
signed main() {
	memset(F,-1,sizeof(F));
	scanf("%lld%lld",&n,&m);
	printf("%lld\n",solve(m)-solve(n-1));
}

HDU4507恨 7 不成妻 为例:
这道题很新颖的地方就在于没有单纯求个数,而是求满足条件的数的平方和。

其实本质是一样的。我们仍然沿用dfs计数的思想,不过记录三个值:满足条件的数的个数,满足条件的数的和,满足条件的数的平方和。

我们可以用 f x , s 1 , s 2 f_{x,s1,s2} fx,s1,s2来表示还剩 x x x位,这个数除末 x x x位以外模 7 7 7 s 1 s1 s1,这个数每一位之和除末 x x x位以外模 7 7 7 s 2 s2 s2时所有与 7 7 7无关的数的末 x x x位的平方和

数学转化
让我们来研究一下 ( x 1 + t ∗ 1 0 y ) 2 + ( x 2 + t ∗ 1 0 y ) 2 + . . . + ( x n + t ∗ 1 0 y ) 2 (x_1+t∗10^y)^2+(x_2+t∗10^y)^2+...+(x_n+t∗10^y)^2 (x1+t10y)2+(x2+t10y)2+...+(xn+t10y)2这个式子。

用完全平方公式展开即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod=1e9+7;
//coding makes a full man
struct Number{
	int num,nc,nc2,vis;
}f[20][7][7];
int n,m,a[20],len;
int fpow(int x,int y) {
	int tot=1;
	for(;y;y>>=1) {
		if(y&1) tot=tot*x%mod;
		x=x*x%mod;
	}
	return tot;
}
Number dfs(int x,int y,int z,int limit) {
	if(x==0) {
		if(y&&z) f[x][y][z].num=1;
		return f[x][y][z];
	} 
	if(!limit&&f[x][y][z].vis!=0) return f[x][y][z];
	int maxn= (limit==1?a[x]:9),ans=0;
	Number u=(Number){};
	for(int i=0;i<=maxn;i++) {
		if(i==7) continue;
		Number v=dfs(x-1,(y+i)%7,(z*10+i)%7,limit&&i==a[x]);
		u.num=(u.num+v.num)%mod;
		u.nc=(u.nc+v.nc+fpow(10,x-1)*i*v.num%mod)%mod;
		u.nc2=(u.nc2+v.nc2+2*i*fpow(10,x-1)%mod*v.nc%mod+fpow(10,2*(x-1))*i*i%mod*v.num%mod)%mod;
	}
	if(!limit) f[x][y][z]=u,f[x][y][z].vis=1;
	return u;
}
int solve(int x) {
	len=0; while(x) a[++len]=x%10,x/=10;
	return dfs(len,0,0,1).nc2;
}
signed main() {
	int T;
	scanf("%lld",&T);
	while(T--) {
		scanf("%lld%lld",&n,&m);
		printf("%lld\n",(solve(m)-solve(n-1)+mod)%mod);
	}
}

SAC#1 - 萌数为例:
n<=1000,但仍然可以用数位dp的框架来做。只不过搜索层数和状态数增加了而已。

#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
//萌数:含回文子串
//若数值大于longlong,则用字符串输入,其操作与正常的数相同
//结果一般要取模(因为数太多) 
int f[1005][10][10][2]; 
int a[1005],len,res1,res2,flag;
//只需分析出一个性质:回文串的长度只可能是2或者3
//前导零不能拿来判回文串 
bool checker() {
	for(int i=2;i<=len;i++) {
		if(a[i]==a[i-1]) return 1;
		if(i>2&&a[i]==a[i-2]) return 1;
	}
	return 0;
}
int dfs(int x,int pre,int pre2,int d,int limit,int zero) {
	if(x<1) return d;
	if(d) pre=pre2=0;
	if(!limit&&!zero&&pre>=0&&pre2>=0&&f[x][pre][pre2][d]!=-1) return f[x][pre][pre2][d];
	int maxn= (limit==1?a[x]:9),ans=0;
	for(int i=0;i<=maxn;i++) {
		ans+=dfs(x-1,pre2,(zero&&!i)?-1:i,d||(!zero&&(i==pre2||i==pre)),limit&&i==a[x],zero&&!i);
		ans%=mod;
	}
	if(!limit&&!zero&&pre>=0&&pre2>=0) f[x][pre][pre2][d]=ans;
	return ans;
}
int solve() {
	len=0; char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') a[++len]=c-'0',c=getchar();
	reverse(a+1,a+1+len);
	return dfs(len,-1,-1,0,1,1);
} 
signed main() {
	memset(f,-1,sizeof(f));
	res1=solve();
	if(checker()) flag=1;
	res2=solve();
	printf("%d",(res2-res1+flag+mod)%mod);
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值