数位dp - 恨7不成妻(HDU4507)

恨7不成妻
描述

单身!
依然单身!
吉哥依然单身!
DS 级码农吉哥依然单身!
所以,他平生最恨情人节,不管是 214 还是 77 ,他都讨厌!
吉哥观察了 214 和 77 这两个数,发现:
2+1+4=7
7+7=7×2
77=7×11
最终,他发现原来这一切归根到底都是因为和 7 有关!所以,他现在甚至讨厌一切和 7 有关的数!
什么样的数和 7 有关呢?如果一个整数符合下面三个条件之一,那么我们就说这个整数和 7 有关:
整数中某一位是 7 ;
整数的每一位加起来的和是 7 的整数倍;
这个整数是 7 的整数倍。
现在问题来了:吉哥想知道在一定区间内和 7 无关的数字的平方和。

输入

输入数据的第一行是测试数据组数 T ,然后接下来的 T 行表示 T 组测试数据。
每组数据在一行内包含两个正整数 L,R

输出

对于每组数据,请计算 [L,R] 中和 7 无关的数字的平方和,并将结果对 109+7取模后输出。

样例输入

3
1 9
10 11
17 17

样例输出

236
221
0

提示

对于全部数据,1≤T≤50,1≤L≤R≤1018


Analysis

虽然我也不知道还有三天就考联赛我做数位dp的题干什么,但这道题确实很妙啊

在看到要求平方和之前,一切都是数位dp的常见套路
然而我们还需要求平方和,这就需要一点技巧了
首先考虑简单一点的,我们来求一次方和(也就是在L~R之间所有满足要求的数的和)
假设当前位为 p o s pos pos,我们填入了一个 i i i,然后递归下去处理,返回的时候得到了当前位为i,后面全部都满足条件的数的个数 c n t cnt cnt
那么加上当前位的贡献,我们的一次方和就变成了
n o w . s u m 1 = c n t ∗ i ∗ 1 0 p o s − 1 + t m p . s u m 1 now.sum1=cnt*i*10^{pos-1}+tmp.sum1 now.sum1=cnti10pos1+tmp.sum1
(now.sum1表示算上当前位的一次方和,tmp.sum1表示不算当前位的一次方和)
现在考虑二次方和(a表示不算当前位,后面的数组成的数)
Σ ( a + i ∗ 1 0 p o s − 1 ) 2 \Sigma{(a+i*10^{pos-1})}^2 Σ(a+i10pos1)2
拆开化简,令 b = i ∗ 1 0 p o s − 1 b=i*10^{pos-1} b=i10pos1就可以得到:
Σ a 2 + 2 ∗ b ∗ Σ a + Σ b 2 \Sigma{a^2}+2*b*\Sigma{a}+\Sigma{b^2} Σa2+2bΣa+Σb2
然后就解决啦~~


Code
#include<bits/stdc++.h>
#define ll long long
#define P 1000000007
using namespace std;
int T,a[20],num=0;
ll l,r,two[20];
struct node{
	ll sum1,sum2,cnt;//sum1-->a^1 sum2-->a^2 cnt-->a^0
	node():sum1(0),sum2(0),cnt(0){}
}f[20][10][10];
inline node dp(int pos,int sum,int num,int limit){
	if(!pos){
		node tmp;
		tmp.cnt=(bool)(sum&&num);
		return tmp;
	}
	if(!limit&&f[pos][sum][num].cnt!=-1) return f[pos][sum][num];
	int top=limit?a[pos]:9;
	node res;
	for(int i=0;i<=top;++i){
		if(i==7) continue;
		node tmp=dp(pos-1,(sum+i)%7,(num*10+i)%7,limit&&i==a[pos]);
		res.cnt=(tmp.cnt+res.cnt)%P;
		res.sum1=(tmp.cnt*1ll*i%P*two[pos-1]%P+tmp.sum1+res.sum1)%P;
		res.sum2=(((res.sum2+tmp.sum2)%P+(2*two[pos-1]%P*1ll*i%P*tmp.sum1)%P)%P+(tmp.cnt*i*i%P*two[pos-1]%P*two[pos-1])%P)%P;	
	}
	if(!limit) f[pos][sum][num]=res;
	return res;
}
inline ll solve(ll x){
	num=0;
	while(x){
		a[++num]=x%10;
		x/=10;
	}
	return dp(num,0,0,1).sum2;
}
int main(){
	memset(f,-1,sizeof(f));
	scanf("%d",&T);
	two[0]=1;
	for(int i=1;i<=20;++i) two[i]=two[i-1]*10%P;
	while(T--){
		scanf("%lld%lld",&l,&r);
		cout<<(solve(r)-solve(l-1)+P)%P<<'\n';
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值