HDU 4507 吉哥系列故事——恨7不成妻 数位dp(超级恶心)

题目链接:HDU 4507 吉哥系列故事——恨7不成妻

题目

Problem Description

单身!
  依然单身!
  吉哥依然单身!
  DS级码农吉哥依然单身!
  所以,他生平最恨情人节,不管是214还是77,他都讨厌!
  
  吉哥观察了214和77这两个数,发现:
  2+1+4=7
  7+7=72
  77=7
11
  最终,他发现原来这一切归根到底都是因为和7有关!所以,他现在甚至讨厌一切和7有关的数!

什么样的数和7有关呢?

如果一个整数符合下面3个条件之一,那么我们就说这个整数和7有关——
  1、整数中某一位是7;
  2、整数的每一位加起来的和是7的整数倍;
  3、这个整数是7的整数倍;

现在问题来了:吉哥想知道在一定区间内和7无关的数字的平方和。

Input

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

Output

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

Sample Input

3
1 9
10 11
17 17

Sample Output

236
221
0

题目分析

超级恶心的一道数位dp,不建议这方面刚入门的选手做,本身细节很多,要是对数位dp再不熟悉,那bug能de到哭。
题意很简单,求一个范围里与7无关的数的平方和。常规的数位dp好像都是求个数的,这里求平方和就是这题的第一个瓶颈(某种意义上,这是最简单的瓶颈了。。。。)
在这里,请确认这样一个基本公式: ∑ i = 1 n ( a + b i ) 2 = n a 2 + 2 a ∑ i = 1 n b i + ∑ i = 1 n b i 2 \sum_{i=1}^{n}(a+b_i)^2 = na^2+2a\sum_{i=1}^nb_i+\sum_{i=1}^{n}b_i^2 i=1n(a+bi)2=na2+2ai=1nbi+i=1nbi2这是这题求平方和的基本原理。这里a是进行dp时增加的高位数 ( i ∗ 1 0 j ) (i*10^j) i10j,这题的状态转移里这个式子很重要。它提供了从低位平方和到高位平方和的基本运算法则。
有了这个式子后,我们很自然地想到需要三个数组,平方和 ( ∑ i = 1 n b i 2 ) (\sum_{i=1}^{n}b_i^2) (i=1nbi2),数字和 ( ∑ i = 1 n b i ) (\sum_{i=1}^nb_i) (i=1nbi),和符合的数个数 ( n ) (n) (n)。dp[i][j][k][m]表示第 i i i位,数字为 j j j % 7 \%7 %7 k k k,各位加起来 % 7 \%7 %7 m m m的数的平方和 / 和 / 个数。
状态转移方程也不难想 a = ( k − j ∗ 1 0 i % 7 + 7 ) % 7 , b = ( m − j + 14 ) % 7 a=(k-j*10^i\%7+7)\%7,b=(m-j+14)\%7 a=(kj10i%7+7)%7,b=(mj+14)%7 c n t [ i ] [ j ] [ k ] [ m ] = ∑ p = 1 9 c n t [ i − 1 ] [ p ] [ a ] [ b ] cnt[i][j][k][m]=\sum_{p=1}^{9}cnt[i-1][p][a][b] cnt[i][j][k][m]=p=19cnt[i1][p][a][b] t p [ i ] [ j ] [ k ] [ m ] = ∑ i = 1 9 ( c n t [ i − 1 ] [ p ] [ a ] [ b ] ∗ j ∗ 1 0 i + t p [ i − 1 ] [ p ] [ a ] [ b ] ) tp[i][j][k][m]=\sum_{i=1}^{9}(cnt[i-1][p][a][b]*j*10^i+tp[i-1][p][a][b]) tp[i][j][k][m]=i=19(cnt[i1][p][a][b]j10i+tp[i1][p][a][b]) d p [ i ] [ j ] [ k ] [ m ] = ∑ i = 1 9 ( c n t [ i − 1 ] [ p ] [ a ] [ b ] ∗ ( j ∗ 1 0 i ) 2 + 2 ∗ j ∗ 1 0 i ∗ t p [ i − 1 ] [ a ] [ b ] + d p [ i − 1 ] [ p ] [ a ] [ b ] ) dp[i][j][k][m]=\sum_{i=1}^{9}(cnt[i-1][p][a][b]*(j*10^i)^2+2*j*10^i*tp[i-1][a][b]+dp[i-1][p][a][b]) dp[i][j][k][m]=i=19(cnt[i1][p][a][b](j10i)2+2j10itp[i1][a][b]+dp[i1][p][a][b])题目中三个条件里,第一,不包含7,那么我们状态转移的时候碰到 j = 7 j=7 j=7直接跳过就好,第二第三都是最后模 7 7 7不为 0 0 0,输出答案的时候保证 k 、 m k、m km不为 0 0 0即可。

坑点

1、最后的差值有可能是负的,所以需要先加上 m o d mod mod再取模。
2、 a % ( 1 e 9 + 7 ) % 7 = a % 7 a\%(1e9+7)\%7 = a\%7 a%(1e9+7)%7=a%7是不一定成立的,所以预处理 1 0 i 10^i 10i数组的时候,需要另外打一个对 7 7 7取模的值的表,不能直接拿模过 ( 1 e 9 + 7 ) (1e9+7) (1e9+7)的值直接计算(当然,这是假定你打表的时候用 t [ i ] = t [ i − 1 ] ∗ 10 % m o d t[i]=t[i-1]*10\%mod t[i]=t[i1]10%mod打,如果打表时没取模,到计算积的时候再取,这条就当屁处理)
3、时刻注意溢出问题
4、如果不做 1 e 18 1e18 1e18位的计算(确实也没意义),那么需要有特判 ( 1 e 18 , 1 e 18 − 1 ) (1e18, 1e18-1) (1e18,1e181)

代码

#include <cstdio>
#include <algorithm>
#include <cstring> 
using namespace std;

#define ll long long
const ll mod = 1e9 + 7; 
ll dp[20][10][7][7], tp[20][10][7][7], cnt[20][10][7][7]; //位数,首位数字,数%7,总和%7
//数组分别是平方和、sum、方案数 
ll tb[20]; 
int num[20], mb[20]; 

void init()
{
	for(int i = 0; i < 10; i++)
		if(i != 7)
			dp[0][i][i%7][i%7] = (ll)i*(ll)i, tp[0][i][i%7][i%7] = (ll)i, cnt[0][i][i%7][i%7] = 1; 
	for(int i = 1; i < 18; i++){
		for(int j = 0; j < 10; j++){
			if(j == 7) continue; 
			for(int k = 0; k < 7; k++){
				for(int m = 0; m < 7; m++){
					int c = (j * mb[i]) % 7, a = (k- c + 7) % 7, b = (m-j+14) % 7; 
					for(int p = 0; p < 10; p++){
						if(p == 7) continue; 
						cnt[i][j][k][m] += cnt[i-1][p][a][b];  
						tp[i][j][k][m] += (cnt[i-1][p][a][b]*tb[i]%mod*(ll)j%mod + tp[i-1][p][a][b]) % mod; 
						dp[i][j][k][m] += ((ll)(j*j)*tb[i] % mod *tb[i]%mod*cnt[i-1][p][a][b] % mod + tb[i]*tp[i-1][p][a][b] % mod*(ll)(2*j) %mod + dp[i-1][p][a][b]) % mod;
						cnt[i][j][k][m] %= mod, tp[i][j][k][m] %= mod, dp[i][j][k][m] %= mod; 
					}
				}
			}
		}
	}
} 

ll query(ll a)
{
	a++;
	ll res = 0;
	if(a > (ll)1e18) {a-- ;res = (a%mod)*(a%mod)%mod;}
	if(a == (ll)1e18) a--;
	ll c = 0; 
	memset(num, 0, sizeof(num));
	int dex = 0, m1 = 0, m2 = 0; 
	while(a){
		num[dex++] = (int)(a % 10);
		a /= 10; 
	} 
	dex--;
	while(dex >= 0){
		int p = (7-m1)%7, q = (7-m2) % 7;
		for(int i = 0; i < num[dex]; i++){
			if(i == 7) continue;
			for(int j = 0; j < 7; j++){
				if(j == p) continue; 
				for(int k = 0; k < 7; k++){
					if(k == q) continue; 
					res += (dp[dex][i][j][k] + 2*tp[dex][i][j][k]% mod *c %mod + cnt[dex][i][j][k]*c%mod*c%mod) % mod;
					res %= mod;
				} 
			} 
		}
		m1 = (m1 + mb[dex]*num[dex]) % 7;
		m2 = (m2 + num[dex]) % 7; 
		if(num[dex] == 7) break; 
		c += tb[dex] * (ll)num[dex] % mod, c %= mod ,dex--;
	}
	return res; 
} 

int main()
{
	memset(dp, 0, sizeof(dp));
	memset(tp, 0, sizeof(tp)); 
	memset(cnt, 0, sizeof(cnt)); 
	tb[0] = 1, mb[0] = 1;
	for(int i = 1; i < 20; i++)
		tb[i] = tb[i-1] * 10 % mod, mb[i] = (10*mb[i-1])%7;
	init(); 
	int t;
	scanf("%d", &t);
	for(int i = 1; i <= t; i++){
		ll s, e;
		scanf("%lld%lld", &s, &e);
		printf("%lld\n", (query(e) - query(s-1) + mod) % mod); 	
	}
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值