- 定义 \(a_{1\sim n}\) 为将 \(1\sim n\) 按字典序从小到大排序后的结果,求 \((\sum_{i=1}^n(i-a_i)\ \operatorname{mod}\ 998244353)\ \operatorname{mod}\ 10^9+7\)。
- \(1\le n\le10^{12}\)
题意转化
这题的求和中有两种不同的取模,看起来非常麻烦。
考虑取模的一个经典转化,即 \(x\ \operatorname{mod}\ y=x-\lfloor\frac xy\rfloor\times y\)。
所以原式可以写成:\((\sum_{i=1}^ni-a_i-\lfloor\frac{i-a_i}{998244353}\rfloor\times998244353)\ \operatorname{mod}\ 10^9+7\)。
发现 \(\sum i-a_i=0\),所以要求的就是 \(\sum_{i=1}^n\lfloor\frac{i-a_i}{998244353}\rfloor\)。
Meet in Middle
考虑 Meet in Middle,枚举最高的 \(6\) 位,则最高 \(6\) 位为枚举值的数肯定在 \(a\) 中是连续的一段。
对于一个长度为 \(6+l\)、较低位在所有可能的较低位中字典序排名为 \(rk\) 的数 \(y\),假设前 \(6\) 位数为 \(x\)、前 \(6\) 位小于 \(x\) 的数共有 \(ct\) 个,则它产生的贡献应该是 \(\lfloor\frac{(ct+rk)-(x10^l+y)}{998244353}\rfloor\),可以拆成只与前 \(6\) 位和 \(l\) 有关的 \(ct-x10^l\) 以及只与较低位有关的 \(rk-y\)。
形如 \(\lfloor\frac{A+B}C\rfloor\) 的式子有个经典拆分方式:\(\lfloor\frac{A+B}C\rfloor=\lfloor\frac AC\rfloor+\lfloor\frac BC\rfloor+\lfloor\frac {(A\ \operatorname{mod}\ C)+(B\ \operatorname{mod}\ C)}C\rfloor\)。
\(\lfloor\frac AC\rfloor\) 和 \(\lfloor\frac BC\rfloor\) 分别只与 \(A,B\) 有关可以各自计算,而 \(\lfloor\frac {(A\ \operatorname{mod}\ C)+(B\ \operatorname{mod}\ C)}C\rfloor\) 在已知 \(A\) 情况下只要求有多少 \(B\ \operatorname{mod}\ C\) 大于等于 \(C-(A\ \operatorname{mod}\ C)\),可以事先拿一个 vector 存下所有 \(B\ \operatorname{mod}\ C\) 并排序,询问时 lower_bound 一下即可。
因此先 dfs 一遍所有可能的较低位,分 \(l\) 存下 \(rk-y\) 并排序。
然后再 dfs 一遍所有可能的最高 \(6\) 位,不足 \(6\) 位直接统计,否则根据预先存下的信息计算。
实现起来可能要注意一些细节,感觉我的写法似乎有点麻烦。
代码:\(O(\sqrt n\log \sqrt n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define SZ 1000000
#define V 998244353
#define X 1000000007
#define LL long long
#define Dec(x) (!x--&&(x+=X))
using namespace std;
LL n,nn,p;int ans,tn[7],c[7][7],s[7][7][SZ+1];
void Init(RI x,RI l,CI k)//预处理较低位,存下所有p-x
{
s[k][l][++c[k][l]]=(++p)-x;if(l==k) return;for(RI i=0;i<=9;++i) Init(x*10+i,l+1,k);
}
void BF(LL x,RI l)//暴力
{
if(x>n) return;++p;ans=(ans+(p-x)/V-(p<x)+X)%X;for(RI i=0;i<=9;++i) BF(x*10+i,l+1);
}
void Solve(RI x,RI l)//枚举最高6位
{
if(l^6) {++p,ans=(ans+(p-x)/V-(p<x)+X)%X;for(RI i=0;i<=9;++i) Solve(x*10+i,l+1);return;}//不足6位,直接统计
if(x==nn) return BF(x,0);//最高6位与n的最高6位相同,直接暴力
RI i,k;LL v;for(k=0,v=x;k<=6&&v<=n;++k,v*=10);--k;//求出较低位最大长度
for(i=0;i<=k;++i) v=p-1LL*x*tn[i],ans=(ans+1LL*(v/V-(v<0)+X)*tn[i])%X,//枚举长度,加上的数为p-x*tn[i]
v=(v%V+V)%V,ans=(ans+tn[i]-(lower_bound(s[k][i]+1,s[k][i]+tn[i]+1,V-v)-s[k][i])+1)%X;//求相加大于等于998244353的数的个数
for(i=0;i<=k;++i) p+=tn[i];//更新排名
}
int main()
{
RI i,k,dc;for(scanf("%lld",&n),tn[0]=i=1;i<=6;++i) tn[i]=tn[i-1]*10;
for(k=0;k<=6;++k) for(p=0,Init(0,0,k),i=0;i<=k;++i) sort(s[k][i]+1,s[k][i]+tn[i]+1);//排序
if(p=0,n<1e6) for(i=1;i<=9;++i) BF(i,1);else {nn=n;W(nn>=1e6) nn/=10;for(i=1;i<=9;++i) Solve(i,1);}//n小直接暴力,否则nn记录前6位
return printf("%d\n",(int)(1LL*(X-ans)*V%X)),0;//答案为-ans*998244353
}