[BZOJ3326][Scoi2013]数数
试题描述
Fish 是一条生活在海里的鱼,有一天他很无聊,就开始数数玩。
他数数玩的具体规则是:
- 确定数数的进制 \(B\)
- 确定一个数数的区间 \([L, R]\)
- 对于 \([L, R]\) 间的每一个数,把该数视为一个字符串,列出该字符串的每一个(连续的)子串对应的B进制数的值。
- 对所有列出的数求和。
现在 Fish 数了一遍数,但是不确定自己的结果是否正确了。由于 \([L, R]\) 较大,他没有多余精力去验证是否正确,你能写一个程序来帮他验证吗?
输入
输入包含三行。
第一行仅有一个数 \(B\),表示数数的进制。
第二行有 \(N + 1\) 个数,第一个数为 \(N\),表示数 \(L\) 在 \(B\) 进制下的长度为 \(N\),接下里的 \(N\) 个数从高位到低位的表示数 \(L\) 的具体每一位。
第三行有 \(M + 1\) 个数,第一个数为 \(M\),表示数 \(R\) 在 \(B\) 进制下的长度为 \(M\),接下里的 \(M\) 个数从高位到低位的表示数 \(R\) 的具体每一位。
输出
输出仅一行,即按照 Fish 数数规则的结果,结果用 \(10\) 进制表示,由于该数可能很大,输出该数模上 \(20130427\) 的模数。
输入示例
10
3 1 0 3
3 1 0 3
输出示例
120
数据规模及约定
\(20\%\) 数据,\(0 \le R \le L \le 10^5\)。
\(50\%\) 数据,\(2 \le B \le 1000\),\(1 \le N,M \le 1000\)。
\(100\%\) 数据,\(2 \le B \le 10^5\),\(1 \le N,M \le 10^5\)。
题解
这题明显是一个数位 dp,但是由于各个范围都很大,我们需要将它强力优化成线性的。
首先假设没有上下界的限制,考虑 \(f(i)\) 表示所有 \(i\) 位数的所有子串和,那么我们就可以尝试递推一下 \(f(i)\),方法就是一位一位加入,然后累计上新增的子串的贡献,像这样
\begin{aligned}
f(i)
& = \sum_{a=0}^{B-1} { f(i-1) + \sum_{j=0}^{i-1} \left( a (B^j)^2 + \frac{B^j (B^j - 1)}{2} \right) B^{i-1-j} } \\
& = \sum_{a=0}^{B-1} { f(i-1) + \sum_{j=0}^{i-1} a B^j B^{i-1} + \frac{B^{i-1} (B^j - 1)}{2} } \\
& = \sum_{a=0}^{B-1} { f(i-1) + \frac{B^{i-1}}{2} \sum_{j=0}^{i-1} (2a B^j + B^j - 1) } \\
& = \sum_{a=0}^{B-1} { f(i-1) + \frac{B^{i-1}}{2} \left( 2a \sum_{j=0}^{i-1} B^j + \sum_{j=0}^{i-1} B^j + \sum_{j=0}^{i-1} 1 \right) }
\end{aligned}
这个 \(\sum_{j=0}^{i-1} B^j\) 可以前缀和预处理出来,我们记它为 \(S_{i-1}\),那么上式变为
\begin{equation}
\label{e1}
\sum_{a=0}^{B-1} { f(i-1) + \frac{B^{i-1}}{2} (2 S_{i-1}a + S_{i-1} - i) }
\end{equation}
整理可得(令 \(sum(l, r) = \sum_{i=l}^r i = \frac{(l + r)(r - l + 1)}{2}\))
\[ f(i) = B \cdot f(i-1) + \frac{B^{i-1}}{2} (2 S_{i-1} \cdot sum(0, B-1) + B \cdot S_{i-1} - B \cdot i) \]
这样就可以线性递推了。
接下来考虑怎样求出 \(0 \sim N\)(\(N\) 是一个 \(B\) 进制数)的答案。首先将位数更低的那些数的子串和累加起来(注意最高位不能是 \(0\),如何处理最高位不能是 \(0\)?用下面的方法),然后考虑一位一位确定上界的每一位,并统计所有的子串和。
\((\ref{e1})\) 式我没有急着展开,就是因为它可以变成更一般的形式,在“一位一位确定上界”的过程中可以用到。现在由于我们确定了上界的前若干位,我们可以看成后 \(i-1\) 位数往前添加的 \(a\) 并不是一位数,而是 \(a \in \{ \overline{N_1 N_2 \cdots N_k 0}, \overline{N_1 N_2 \cdots N_k 1}, \overline{N_1 N_2 \cdots N_k 2}, \cdots ,\overline{N_1 N_2 \cdots N_k B'} \} = A\),其中 \(B’ = N_{k+1} - 1\),\(N_i\) 表示 \(N\) 从高到低的第 \(i\) 位。
那么 \((\ref{e1})\) 可以变成
\begin{equation}
\label{e2}
\sum_{a \in A} { f(i-1) + \frac{B^{i-1}}{2} (2 S_{i-1} \cdot calc(a) + S_{i-1} - i) }
\end{equation}
注意上面的 \(a\) 变成了 \(calc(a)\),它的意思是 \(a\) 的所有后缀表示的数的总和。为什么要这样变?我们发现其实这里要统计的就是那些“跨过分界点”的子串,分界点就是 \(N\) 的第 \(k+1\) 位,这一位之后的是可以任意取的,这一位之前就是 \(A\) 中的数。
我们需要把 \((\ref{e2})\) 变成一个 \(O(1)\) 的形式
\[ |A| \cdot f(i-1) + \frac{B^{i-1}}{2} (2 S_{i-1} \cdot \sum_{a \in A} calc(a) + |A| \cdot S_{i-1} - |A| \cdot i) \]
这个 \(\sum_{a \in A} calc(a)\) 可以在确定每一位上界的时候通过计算“后缀代表的数的和”得到,就是一个简单的递推。
经过上面的计算,我们还差子串在前 \(k\) 位中的没有计算,这一部分只需要在一位一位确定上界的过程中每加入以为就把所有后缀代表的数的和乘上 \(B^{i-1}\) 累加即可(\(i-1\) 表示后面没有确定的位数,因为后面没有限制所以有 \(B^{i-1}\) 这么多个数)。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 100010
#define MOD 20130427
#define inv2 10065214
#define LL long long
int B, pB[maxn], spB[maxn], f[maxn];
LL sum(int l, int r) { return ((LL)(l + r) * (r - l + 1) >> 1) % MOD; }
int n, num[maxn], SufNum[maxn], PreNum[maxn];
int calc() {
if(n == 1 && !num[1]) return 0;
int ans = 0;
rep(i, 1, n - 1) ans += (LL)(B - 1) * f[i-1] % MOD + (LL)pB[i-1] * inv2 % MOD * (2ll * spB[i-1] * sum(1, B - 1) % MOD + (LL)(B - 1) * spB[i-1] % MOD - (LL)(B - 1) * i % MOD + MOD) % MOD, ans %= MOD;
int suf = 0, tt = 0;
SufNum[0] = 0;
rep(i, 1, n) {
SufNum[i] = SufNum[i-1] + (LL)num[i] * pB[i-1] % MOD;
if(SufNum[i] >= MOD) SufNum[i] -= MOD;
}
rep(i, 0, n) {
SufNum[i]++;
if(SufNum[i] >= MOD) SufNum[i] -= MOD;
}
PreNum[n+1] = 0;
dwn(i, n, 1) {
PreNum[i] = ((LL)PreNum[i+1] * B + num[i]) % MOD;
if(PreNum[i] >= MOD) PreNum[i] -= MOD;
}
dwn(i, n, 1) {
int x = i == n ? num[i] - 1 : num[i], cnta = (LL)x * (n - i + 1) % MOD, suma = (LL)suf * B % MOD * x % MOD + sum(i == n ? 1 : 0, num[i] - 1) * (n - i + 1) % MOD; if(suma >= MOD) suma -= MOD;
ans += (LL)x * f[i-1] % MOD + (LL)pB[i-1] * inv2 % MOD * (2ll * spB[i-1] * suma % MOD + (LL)cnta * spB[i-1] % MOD - (LL)cnta * i % MOD + MOD) % MOD; ans %= MOD;
suf = (LL)suf * B % MOD + (LL)num[i] * (n - i + 1) % MOD; if(suf >= MOD) suf -= MOD;
ans += (LL)suf * SufNum[i-1] % MOD; if(ans >= MOD) ans -= MOD;
}
return ans;
}
int main() {
B = read();
pB[0] = spB[0] = 1;
rep(i, 1, maxn - 1) {
pB[i] = (LL)pB[i-1] * B % MOD;
spB[i] = spB[i-1] + pB[i]; if(spB[i] >= MOD) spB[i] -= MOD;
f[i] = (LL)B * f[i-1] % MOD + (LL)pB[i-1] * inv2 % MOD * (2ll * spB[i-1] * sum(0, B - 1) % MOD + (LL)B * spB[i-1] % MOD - (LL)B * i % MOD + MOD) % MOD;
if(f[i] >= MOD) f[i] -= MOD;
}
int ans = 0;
n = read(); dwn(i, n, 1) num[i] = read();
if(n == 1 && num[1] <= 1) ;
else {
num[1]--; rep(i, 1, n - 1) if(num[i] < 0) num[i] += B, num[i+1]--;
if(!num[n]) n--;
ans = -calc();
}
n = read(); dwn(i, n, 1) num[i] = read();
if(n == 1 && !num[1]) return puts("0"), 0;
ans += calc();
ans = (ans % MOD + MOD) % MOD;
printf("%d\n", ans);
return 0;
}