uoj140 【UER #4】被粉碎的数字

题目

看起来就像是数位( m dp)

不妨从竖式乘法的角度来考虑这个问题

为了方便处理进位,我们得从低位向高位填数

(dp[i][0/1][j][p][t])表示填到了第(i)位,卡不卡上界,(f(x)=j)(f(k imes x)=p)(不计算最高位),需要向最高位进(t)(x)有多少个

这里的卡上界比较奇怪,如果这一位上填的数大于(R)这一位上的数,那么就一定卡了上界,如果小于这一位上的数,那么就一定不卡上界,如果和原来的数相等,那么就继续之前的状态

所以这个(0/1)就表示后面的(i)为和(R)(i)位的大小关系,如果填的数大于(R)(i)为,那么这个状态就是(1);否则就是(0)

至于转移,就比较简单,我们枚举这一位上填什么数(y),那么对于(x),数位和增加了(y),对于(k imes x),这一位上直接来一个乘法是(k imes y),还有之前的进位(t),于是就是((k imes y+t)\% 10),新的进位就是((k imes y+t)/10)

但是这样我们填到(R)的最高位之后可能还有一些进位没有处理,于是再往前多处理(3)位把进位处理完

最后的答案就是(sum_{i}dp[lg_{R}][0][i][i][0]),即(x)的数位和等于(f(k imes x))的情况,但是这样多了一维非常难受,考虑的最后只要求(j=p),所以我们直接把(j-p)看成一维状态就好了

代码

#include<bits/stdc++.h>
#define re register
#define LL long long
LL dp[25][2][1000][500];
int m,w,a[25],M=250;LL n;
inline void split(LL x) {
	while(x) a[++w]=(x%10),x/=10;
}
int main() {
	scanf("%lld%d",&n,&m);
	dp[0][0][0][M]=1;
	split(n);
	for(re int i=0;i<w+3;i++)
		for(re int j=0;j<2;++j)
			for(re int k=0;k<1000;++k)
				for(re int p=M-2*i*9;p<=M+2*i*9;++p) {
					if(!dp[i][j][k][p]) continue;
					for(re int t=0;t<10;++t)
						dp[i+1][t==a[i+1]?j:t>a[i+1]][(k+t*m)/10][p+t-(k+t*m)%10]+=dp[i][j][k][p];
				} 
	printf("%lld
",dp[w+3][0][0][M]-1);
	return 0;
}
原文地址:https://www.cnblogs.com/asuldb/p/11455315.html