BZOJ 1799 self同类分布【数位dp】


传送门


SOL

“数字之和整除原数”
·············一股浓厚的小学数学的气息。。


递推:

第一维 :位数 第二维:个位数字总和
最开始可能会想去推点数论结论方便直接转移,发现只有位数 和 总和 难以实现”拼数“

突然发现 取模 可以表示除法的结果 ,
把数字总和看成mod,两个数模数相加 等于 mod 即 整除 (这个好像是常规操作。。)
所以在后面递归拼数的过程中

原始思路方程 :
(预处理中)

f [ i ] [ j ] [ S u m ] [ l ] = ∑ p = 0 9 f [ i − 1 ] [ j − p ] [ S u m ] [ ( l − p ∗ 1 0 i − 1 ) m o d S u m ] f[i][j][Sum][l] = \sum_{p=0}^9 f[i-1][j-p][Sum][(l-p*10^{i-1}) mod Sum] f[i][j][Sum][l]=p=09f[i1][jp][Sum][(lp10i1)modSum]

i:第几位 j:数字总和 sum:模数 l:原数取模的余数

发现第三维 sum明显 可以 压掉 (不同sum之间单独存在)
(主要是四维的话,空间也开不下。。)

接下来是拼数的方程:

t :: a + b + c a+b+c a+b+c, q : a b c ‾ \overline{abc} abc m o d S u m modSum modSum
表示前面已经产生的总和

转移时:

i f ( p &lt; m a x N u m ) r e t + = f [ i − 1 ] [ S u m − t − p ] [ ( S u m − q − p ∗ 1 0 i − 1 ) m o d S u m ] if(p&lt;maxNum)ret+= f[i-1][Sum-t-p][(Sum-q-p*10^{i-1})modSum ] ifp<maxNum)ret+=f[i1][Sumtp][(Sumqp10i1)modSum]

i f ( p = = m a x N u m ) t + = p ; q + = ( p ∗ 1 0 i − 1 ) m o d S u m if(p==maxNum) t+=p;q+=(p*10^{i-1})mod Sum if(p==maxNum)t+=p;q+=(p10i1)modSum


记忆化:

直接按数字总和枚举即可
(必须要固定数字总和,否则无法表示原数除总和的结果)


CODE

#include<bits/stdc++.h>
#define pf printf
#define sf scanf
#define int long long
using namespace std;
int f[20][200][200],fc[20];
int mod,bit[20],top=0;
inline int chn(int k){
	return (k%mod+mod)%mod;
}
inline void pre(){
	memset(f,0,sizeof f);
	f[0][0][0]=1;
	for(int i=1;i<=top;++i){
		for(int j=0;j<=mod;++j){
			for(int l=0;l<=mod;++l){
				for(int p=0;p<=9;++p){
					if(j<p)break;
					f[i][j][l]+=f[i-1][j-p][chn(l-p*fc[i-1])];
				}
			}
		}
	}
}
inline int work(int R){
	int ret=0;
	top=0;
	while(R)bit[++top]=R%10,R/=10;
	for(mod=1;mod<=9*(top-1)+bit[top];++mod){
		pre();
		int t=0,q=0;
		for(int loc=top;loc>=1;--loc){
			if(mod<t)break;
			int i;
			for(i=0;i<bit[loc];++i){
				if(mod-t<i)break;
				ret+=f[loc-1][mod-t-i][chn(mod-q-i*fc[loc-1])];
			}
			i=bit[loc];
			q=(q+i*fc[loc-1]%mod)%mod;t+=i;
		}
		ret+=(q%mod==0&&t==mod);
	}
	return ret;
}

signed main (){
	int l,r;sf("%lld%lld",&l,&r);
	fc[0]=1;
	for(int i=1;i<=18;++i)fc[i]=fc[i-1]*10;
	pf("%lld",work(r)-work(l-1));

	   return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值