bzoj1799同类分布——数位DP
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1799
数位DP。
1、循环方法
预处理出每个位数上,和为某个数,模某个数余某个数的所有情况;
因为开四维会爆空间,所以省去模数,为此需要固定模数一次一次累加;
余数的转移,以及可以填数的范围都值得注意。
代码如下:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; ll l,r,mul[22],f[22][205][205],ans,sx; int a[3][22],w[3]; void cl(int t,ll tmp) { while(tmp) { a[t][++w[t]]=tmp%10; tmp/=10; } } void pw() { mul[0]=1; for(int i=1;i<w[1];i++)//< mul[i]=mul[i-1]*10; } void pre(int s) { memset(f,0,sizeof f); f[0][0][0]=1; ll lm=0; for(int i=1;i<w[1];i++)//< { lm=min(s,i*9); for(int j=0;j<=lm;j++) for(int l=0;l<s;l++) for(int p=0;p<=9&&j-p>=0;p++) // f[i][j][l]+=f[i-1][j-p][((s-(l-p*mul[i-1]))%s+s)%s]; f[i][j][l]+=f[i-1][j-p][((l-p*mul[i-1])%s+s)%s]; } } ll cal(int t,int s) { ll cnt=0,lj=0; int ss=0; for(int i=w[t];i;i--) { int lm=a[t][i]; if(i==1)lm++; for(int p=0;p<lm&&s-ss-p>=0;p++) cnt+=f[i-1][s-ss-p][(s-(lj+p*mul[i-1])%s)%s];// ss+=a[t][i];lj+=a[t][i]*mul[i-1]; } return cnt; } int main() { scanf("%lld%lld",&l,&r); cl(0,l-1);cl(1,r); pw(); sx=(w[1]-1)*9+a[1][w[1]]; for(int s=1;s<=sx;s++) { pre(s); ans+=cal(1,s)-cal(0,s); } printf("%lld",ans); return 0; }
2、递归方法
记忆化,代码极简单,详见注释。
代码如下:
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; int a[25],mod,cnt,tot; ll f[3][25][205][205],vis[3][25][205][205],x,y;//tot时间戳 ll dp(int p,int d,int s,int v)//p为有无限制,d为第几位,s为位数和,v为模余数 { if(!d)return (!s&&!v);//边界 if(vis[p][d][s][v]==tot)return f[p][d][s][v];//记忆化 vis[p][d][s][v]=tot; ll l=max(0,s-(d-1)*9),r=min(s,((p)?a[d]:9));// ll cnt=0; for(int i=l;i<=r;i++) cnt+=dp((p&(i==a[d])),d-1,s-i,(10*v+i)%mod);//%mod而非%s return f[p][d][s][v]=cnt; } ll solve(ll x) { for(cnt=0;x;x/=10)a[++cnt]=x%10; ll tmp=0; for(mod=1;mod<=cnt*9;mod++)//cnt*9而非a[cnt]*9 tot++,tmp+=dp(1,cnt,mod,0);//一开始就有限制 return tmp; } int main() { scanf("%lld%lld",&x,&y); printf("%lld",solve(y)-solve(x-1)); return 0; }