BZOJ1799 [Ahoi2009]self 同类分布
Description
给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数。
Sample Input
10 19
Sample Output
3
HINT
【约束条件】1 ≤ a ≤ b ≤ 10^18
题解Here!
本蒟蒻表示不会数位$DP$,药丸。。。
首先把询问拆成$sum(b)-sum(a-1)$应该都会。
然后本蒟蒻就不会了。。。
本题记录前面的数字的和,同时还要知道最后产生的数字是否整除和。
记录各位数字的和比较容易,共$9\times 18$个状态。
关键是如何知道已经产生 的数位构成的数字是否整除最后的总和,比较麻烦。
考虑求模,整除的模数为$0$,可以记录已经产生的数字的模数,但是又不知道最后的数字总和是多少,这个模数怎么记录?
可以这样定义:
$dp[i][sum][m][mod]$表示到第i位,前面数字总和为$sum$ ,$\mod mod$的值为$m$,然后枚举$mod$即可计算。
这样可以解决问题,但是计算内存发现:$dp[20][200][200][200]$需要1G的内存,只好想办法压 缩空间,因为$mod$是要在程序中枚举的,所以不必记录这一维状态,这样空间就足够了。
$dp[i][sum][m][0/1]$表示到第$i$位,前面数字总和为$sum$,$\mod mod$的值为$m$,0表示没卡上界,1表示卡了上界,然后枚举$mod$即可计算
写数位dp的时候,我习惯考虑从当前状态进行拓展,也就是所谓的刷表大法。
附代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #define MAXN 210 #define MAXM 30 using namespace std; int top,bit[MAXM]; long long a,b,dp[MAXM][MAXN][MAXN][2]; inline long long read(){ long long date=0,w=1;char c=0; while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();} while(c>='0'&&c<='9'){date=date*10+c-'0';c=getchar();} return date*w; } long long solve(long long x){ long long ans=0; top=0; while(x){bit[++top]=x%10;x/=10;} reverse(bit+1,bit+top+1); for(int sum=1;sum<=top*9;sum++){ memset(dp,0,sizeof(dp)); dp[0][0][0][1]=1; for(int i=0;i<top;i++) for(int j=0;j<=sum;j++) for(int k=0;k<=sum;k++) for(int c=0;c<=1;c++){ if(!dp[i][j][k][c])continue; for(int l=0;l<=(c?bit[i+1]:9);l++){ if(j+l>sum)break; dp[i+1][j+l][(k*10+l)%sum][c&(l==bit[i+1])]+=dp[i][j][k][c]; } } ans+=dp[top][sum][0][0]+dp[top][sum][0][1]; } return ans; } int main(){ a=read();b=read(); printf("%lld\n",solve(b)-solve(a-1)); return 0; }