【hdu4507】吉哥系列故事——恨7不成妻 数位dp

题目描述

求 $[L,R]$ 内满足:数位中不包含7、数位之和不是7的倍数、本身不是7的倍数 的所有数的平方和 mod $10^9+7$ 。

输入

输入数据的第一行是case数T(1 <= T <= 50),然后接下来的T行表示T个case;每个case在一行内包含两个正整数L, R(1 <= L <= R <= 10^18)。

输出

请计算[L,R]中和7无关的数字的平方和,并将结果对10^9 + 7 求模后输出。

样例输入

3
1 9
10 11
17 17

样例输出

236
221
0


题解

数位dp

由于要求数位之和不是7的倍数,因此要维护数位之和模7的值。

由于要求本身不是7的倍数,因此要维护本身模7的值。

所以设 $f[i][j][k][l]$ 表示 $i$ 位数,最高位为 $j$ ,数位之和模7为 $k$ ,本身模7为 $l$ ,且数位中不出现7的......的什么?

题目所求为平方和,而每个数都加上一个数后,平方和不能直接加。

考虑到一个公式: $(a+b)^2=a^2+2ab+b^2$ ,因此 $sum(a+v)^2=sum a^2+2vsum a+v^2sum a^0$ ,所以维护0次方和(即个数)、1次方和及2次方和即可实现给每个数加上一个数。

具体可以使用结构体维护。

然后就是数位dp的老套路:先处理位数不满的,统计到答案中;然后从高位到低位枚举第几位,该位小于原数的该位则一定满足区间条件,把符合条件的数加到答案中。

本题中dp时需要记录前面的数位之和、前面的数(加到答案时还需要加上该数),并且当出现7时直接跳出。

把询问转化为 $[1,n)$的区间更容易一些。

注意一下 $10^{19}$ 爆long long,因此找位数时还要判断当位数等于19时直接跳出(比竟无法存储 $10^{19}$ )。

同时还要注意一下取模问题。

#include <cstdio>
#define mod 1000000007
typedef long long ll;
struct data
{
	ll v[3];
	ll &operator[](ll a) {return v[a];}
	data() {v[0] = v[1] = v[2] = 0;}
	data operator+=(data a)
	{
		v[0] = (v[0] + a[0]) % mod;
		v[1] = (v[1] + a[1]) % mod;
		v[2] = (v[2] + a[2]) % mod;
		return *this;
	}
	data operator+(ll a)
	{
		data ans;
		a %= mod;
		ans[0] = v[0];
		ans[1] = (v[1] + a * v[0]) % mod;
		ans[2] = (v[2] + 2 * a * v[1] + a * a % mod * v[0]) % mod;
		return ans;
	}
}f[19][10][7][7];
ll b[19];
void init()
{
	int i , j , k , l , m;
	f[0][0][0][0][0] = b[0] = 1;
	for(i = 1 ; i < 19 ; i ++ )
	{
		b[i] = b[i - 1] * 10;
		for(j = 0 ; j < 10 ; j ++ )
			for(k = 0 ; k < 10 ; k ++ )
				for(l = 0 ; l < 7 ; l ++ )
					for(m = 0 ; m < 7 ; m ++ )
						if(j != 7)
							f[i][j][(l + j) % 7][(m + j * b[i - 1]) % 7] += f[i - 1][k][l][m] + j * b[i - 1];
	}
}
ll calc(ll n)
{
	int i , j , k , l , s = 0 , now , di = 1;
	ll v = 0;
	data ans;
	for(i = 1 ; i < 19 && b[i] <= n ; i ++ )
		for(j = 1 ; j < 10 ; j ++ )
			for(k = 1 ; k < 7 ; k ++ )
				for(l = 1 ; l < 7 ; l ++ )
					ans += f[i][j][k][l];
	for( ; i ; i -- )
	{
		now = n / b[i - 1] % 10;
		for(j = di ; j < now ; j ++ )
			for(k = 0 ; k < 7 ; k ++ )
				for(l = 0 ; l < 7 ; l ++ )
					if((k + s) % 7 && (l + v) % 7)
						ans += f[i][j][k][l] + v;
		if(now == 7) break;
		s = (s + now) % 7 , v += now * b[i - 1] , di = 0;
	}
	return ans[2];
}
int main()
{
	init();
	int T;
	ll n , m;
	scanf("%d" , &T);
	while(T -- ) scanf("%lld%lld" , &n , &m) , printf("%lld
" , (calc(m + 1) - calc(n) + mod) % mod);
	return 0;
}
原文地址:https://www.cnblogs.com/GXZlegend/p/7813613.html