原题链接:
HDU:点我QωQ
题意简述
定义一个正整数和 7 7 7有关,满足以下三个之一:
- 某一位是 7 7 7
- 数位和是 7 7 7的倍数
- 原数是 7 7 7的倍数
那么就和 7 7 7有关。给定 Q Q Q和 Q Q Q个区间 [ l , r ] [l,r] [l,r],请求出 l , r l,r l,r里面和 7 7 7无关的数的平方和。
数据
输入
第一行是一个
Q
(
Q
<
=
50
)
Q(Q<=50)
Q(Q<=50)
接下来
Q
Q
Q行每行两个正整数
l
,
r
(
1
<
=
l
,
r
<
=
1
e
18
)
l,r(1<=l,r<=1e18)
l,r(1<=l,r<=1e18)
输出
对于每个询问,输出答案。
样例
输入
3
1 9
10 11
17 17
输出
236
221
0
思路
写数位 D P DP DP最容易的事情就是写挂,真的。。。
我们先来设 d p dp dp状态。一开始我状态设多了,导致我转移都不会转移。。。来考虑几个状态,我们看看要不要:
- 位数:这个显然要用
- 首位:重要么?这个题中,如果我们要找和 7 7 7无关的,那么数位中肯定没有 7 7 7。在数位方面,我们只要保证这一点即可。为了保证这一点,我们只要在循环的时候不去搜 7 7 7即可。所以首位不用存。
- 有没有 7 7 7:同理,也不用。
- 位数和膜 7 7 7的余数:显然需要
- 原数膜 7 7 7的余数:同理,需要。
所以就是三维,
d
p
[
i
]
[
j
]
[
k
]
dp[i][j][k]
dp[i][j][k]表示长度为
i
i
i,位数和余数是
j
j
j,原数余数是
k
k
k。
当然,一个
d
p
dp
dp的答案还不能就是一个数:平方和。我们在推转移式子的时候,我们会发现:我们也要维护好满足条件的个数和一次方和。这个也不是不能维护,所以我们考虑打个结构体,都维护上。
然后这个 d p dp dp是干啥的呢?用来记忆化搜索的。搜索时候的状态,也就是长度,位数和余数,原数余数。然后我们现在设长度是 p o s pos pos,当前已经确定的位(有 c n t − p o s + 1 cnt-pos+1 cnt−pos+1位)的数字和膜 7 7 7是 s t a t e state state,当前已经确定的数膜 7 7 7的余数 r r r。
然后,转移的时候这么转移:
- 枚举下一位 i i i(不能是 7 7 7)
- 此时由于我们多确定了一位,位数和便加上了我们枚举的新的位 i i i,当然,确定的数和便先 ∗ 10 *10 ∗10,再加上了 i i i。(和快读的思想类似)
- 继承一些值。
设当前答案是 a n s ans ans,子状态的答案是 t m p tmp tmp。这个答案是一个结构体,里面包含个数 c n t cnt cnt,一次方和 s 1 s1 s1,二次方和 s 2 s2 s2。
显然 a n s . c n t + = t m p . c n t ans.cnt+=tmp.cnt ans.cnt+=tmp.cnt(此处省略取膜)
我们考虑一下, t m p tmp tmp里面的和是 s 1 s1 s1,现在相当于 t m p tmp tmp中每个数都加上了最高位的权(设为 h i g h high high,即 i ∗ 1 0 p o s − 1 i*10^{pos-1} i∗10pos−1),所以就加上了 t m p . c n t tmp.cnt tmp.cnt个 h i g h high high。即 a n s . s 1 + = t m p . s 1 + h i g h ∗ t m p . c n t ans.s1+=tmp.s1+high*tmp.cnt ans.s1+=tmp.s1+high∗tmp.cnt
继续推式子。当然,这个是本题中最毒瘤的式子。 t m p tmp tmp里面的平方和是 s 2 s2 s2,设这个值是 x 1 2 + x 2 2 . . . + x c n t 2 x_1^2+x_2^2...+x_{cnt}^2 x12+x22...+xcnt2,其中 x 1 , x 2 . . . x c n t x_1,x_2...x_{cnt} x1,x2...xcnt是 t m p tmp tmp中那些和 7 7 7无关的数, c n t cnt cnt也就是 t m p . c n t tmp.cnt tmp.cnt。那么,每个数都加上了 h i g h high high,就变成:
( x 1 + h i g h ) 2 + ( x 2 + h i g h ) 2 . . . + ( x c n t + h i g h ) 2 (x_1+high)^2+(x_2+high)^2...+(x^{cnt}+high)^2 (x1+high)2+(x2+high)2...+(xcnt+high)2
= ( x 1 2 + x 2 2 . . . + x k 2 ) + 2 x 1 h i g h + 2 x 2 h i g h . . . + 2 x c n t h i g h + c n t ∗ h i g h 2 =(x_1^2+x_2^2...+x_k^2)+2x_1high+2x_2high...+2x_{cnt}high+cnt*high^2 =(x12+x22...+xk2)+2x1high+2x2high...+2xcnthigh+cnt∗high2
= t m p . s 2 + 2 h i g h ∗ t m p . x 1 + h i g h 2 ∗ t m p . c n t =tmp.s2+2high*tmp.x1+high^2*tmp.cnt =tmp.s2+2high∗tmp.x1+high2∗tmp.cnt
这下就珂以算了。为了避免式子过于长,我们把后面两个(出来 t m p . s 2 tmp.s2 tmp.s2那两个)分成两个变量计算。
还有一点要注意,我们的数位 D P DP DP本质是记忆化搜索,但并不是所有时候都能记忆化的。有一种特殊情况,就是前缀的情况。举个栗子,我们要算 1 1 1到 123546 123546 123546的和,那么我们在枚举到 1235 ∗ ∗ 1235** 1235∗∗( ∗ ∗ ** ∗∗表示还没确定)的时候,就需要特殊判断,不能直接继承答案。这个能不能继承,我们用一个参数 l i m lim lim表示。这个 l i m lim lim的传递么。。。就是每次枚举到和当前位相同的时候传递为真,否则是假。
边界就是我们枚举的位数长度到了 0 0 0。如果两个余数都不为 0 0 0,那么我们有一个解,就是 0 0 0。此时 c n t = 1 , s 1 = 0 , s 2 = 0 cnt=1,s1=0,s2=0 cnt=1,s1=0,s2=0。否则就是一个解也没有, c n t = s 1 = s 2 = 0 cnt=s1=s2=0 cnt=s1=s2=0。
代码:
#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
#define int long long
#define N 25
#define mod 1000000007
struct node
{
int cnt;
int s1,s2;
}dp[N][7][7];
int p10[N];
int d[N],cnt;
node DFS(int pos,bool lim,int state,int r)
{
if (pos==0)//边界
{
if (r!=0 and state!=0)
{
return (node){1,0,0};
}
else
{
return (node){0,0,0};
}
}
if (!lim and dp[pos][state][r].s1!=0)//珂以继承的情况:
//1. 不是前缀
//2. 答案算过
//两个必须都成立才珂以继承
{
return dp[pos][state][r];
}
int up=lim?d[pos]:9;
//如果当前是前缀,就只能取到d[pos]了,多了就超过了x。
//如果不是前缀,说明:
//1. 长度和x一样,某一位比x小
//2. 位数比x小
//反正这两种情况,在枚举的时候,后面的位都是没有限制的,因为他们"输在了起跑线(前面的位)上"
node ans;ans=(node){0,0,0};
for(int i=0;i<=up;++i)
{
if (i==7) continue;
int s=(state+i)%7,m=(r*10%7+i)%7;
//多确定一位i,所以是加上去的
//(这个东西我开始的时候居然理解不了。。。我现在感觉我当时是智障)
node tmp=DFS(pos-1,(lim&&i==up),s,m);
//当且仅当我们现在是前缀
//并且下一位也是和x一样时
//lim继续为1
//否则是0
int high=i*p10[pos-1]%mod;
//新确定的这一位的权值
ans.cnt+=tmp.cnt;ans.cnt%=mod;
ans.s1+=(tmp.s1+high*tmp.cnt%mod);ans.s1%=mod;
int k1=high*high%mod*tmp.cnt%mod;
int k2=2*high%mod*tmp.s1%mod;
ans.s2+=((k1+k2)%mod+tmp.s2%mod);ans.s2%=mod;
//刚才的方程+老多老多的%
}
if (!lim) dp[pos][state][r]=ans;
return ans;
}
int calc(int x)
{
cnt=0;memset(d,0,sizeof(d));
while(x)
{
d[++cnt]=x%10;
x/=10;
}//分解位
node tmp=DFS(cnt,1ll,0ll,0ll);
return tmp.s2%mod;//计算答案
}
void Init()
{
for(int i=0;i<=18;++i)
{
p10[i]=(i==0)?1:p10[i-1]*10;
p10[i]%=mod;
}//打表快速幂
}
void Main()
{
if (0)
{
freopen("","r",stdin);
freopen("","w",stdout);
}
Init();
int t;scanf("%lld",&t);
for(int i=1;i<=t;++i)
{
int l,r;scanf("%lld%lld",&l,&r);
printf("%lld\n",(calc(r)-calc(l-1)+mod)%mod);
}
}
#undef N //25
#undef int //long long
#undef mod //1000000007
};
int main()
{
Flandle_Scarlet::Main();
return 0;
}