『题解』Luogu-P3312 [SDOI2014]数表
Description
- 多测,\(Q\) 组数据。
- 有一张 \(n\times m\) 的数表,其第 \(i\) 行第 \(j\) 列(\(1\le i\le n, 1\le j\le m\))的数值为能同时整除 \(i\) 和 \(j\) 的所有自然数之和。给定 \(a\),计算数表中不大于 \(a\) 的数之和模 \(2^{31}\) 的值。
- \(1\le n, m\le 10^5, 1\le Q\le 2\times 10^4, |a| \le 10^9\)。
Solution
首先模 \(2^{31}\) 就直接用 \(\text{int}\) 存自然溢出,最后和 \(2^{31} - 1\) 取 & 即可。
因为 \(k\mid n \land k\mid m \Leftrightarrow k\mid \gcd(n, m)\),所以第 \(i\) 行第 \(j\) 列的数是 \(\sigma(\gcd(i, j))\)。
先不考虑关于 \(a\) 的限制,则所求为
\[\begin{aligned}
ans
& = \sum_{i = 1}^n \sum_{j = 1}^m \sigma(\gcd(i, j)) \\
& = \sum_{i = 1}^n \sum_{j = 1}^m \sum_{d = 1}^n \sigma(d) [\gcd(i, j) = d] \\
& = \sum_{d = 1}^n \sigma(d) \sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor} \sum_{j = 1}^{\left\lfloor\frac{m}{d}\right\rfloor} [\gcd(i, j) = 1] \\
& = \sum_{d = 1}^n \sigma(d) \sum_{i = 1}^{\left\lfloor\frac{n}{d}\right\rfloor} \sum_{j = 1}^{\left\lfloor\frac{m}{d}\right\rfloor} \sum_{k\mid \gcd(i, j)} \mu(k) \\
& = \sum_{d = 1}^n \sigma(d) \sum_{k = 1}^{\left\lfloor\frac{n}{d}\right\rfloor} \mu(k) \left\lfloor\dfrac{n}{dk}\right\rfloor \left\lfloor\dfrac{m}{dk}\right\rfloor \\
& = \sum_{T = 1}^n \left\lfloor\dfrac{n}{T}\right\rfloor \left\lfloor\dfrac{m}{T}\right\rfloor \sum_{d\mid T} \sigma(d) \mu\left(\dfrac{T}{d}\right)
\end{aligned}
\]
令
\[f(n) = \sum_{d\mid n} \sigma(d) \mu\left(\dfrac{T}{d}\right)
\]
如果没有关于 \(a\) 的限制,我们有 \(f = \sigma * \mu = \operatorname{Id}\)。
加上限制后,需要满足的是 \(\sigma(d) \le a\)。
我们运用莫队的思想,将所有的 \(\sigma\) 以及 \(a\) 从小到大排序,枚举 \(d\) 的倍数加贡献。
我们需要对 \(f\) 进行单点修改加贡献,数论分块区间查询,直接用树状数组即可。
预处理要枚举 \(n\ln n\) 次,还要修改,所以是 \(\Omicron(n\ln n\log n)\) 的;
查询有 \(\sqrt{n}\) 个块,块内要 \(\log n\) 查区间和,所以是 \(\Omicron(Q \sqrt{n} \log n)\) 的。
总时间复杂度为 \(\Omicron(n\log^2 n + Q \sqrt{n} \log n)\)。
Code
//18 = 9 + 9 = 18.
#include <iostream>
#include <cstdio>
#include <algorithm>
#define Debug(x) cout << #x << "=" << x << endl
typedef long long ll;
using namespace std;
const int MAXN = 1e5 + 5;
const int N = 1e5;
typedef int arr[MAXN];
arr p, mu, sigma, num;
bool vis[MAXN];
pair<int, int> f[MAXN];
void pre()
{
mu[1] = sigma[1] = 1;
for (int i = 2; i <= N; i++)
{
if (!vis[i])
{
p[++p[0]] = i;
mu[i] = -1;
sigma[i] = num[i] = 1 + i;
}
for (int j = 1; j <= p[0] && i * p[j] <= N; j++)
{
vis[i * p[j]] = true;
if (i % p[j] == 0)
{
mu[i * p[j]] = 0;
sigma[i * p[j]] = sigma[i] / num[i] * (num[i] * p[j] + 1);
num[i * p[j]] = num[i] * p[j] + 1;
break;
}
mu[i * p[j]] = mu[i] * mu[p[j]];
sigma[i * p[j]] = sigma[i] * sigma[p[j]];
num[i * p[j]] = 1 + p[j];
}
}
for (int i = 1; i <= N; i++)
{
f[i] = {sigma[i], i};
}
sort(f + 1, f + N + 1);
}
arr c;
inline int lowbit(int x)
{
return x & -x;
}
void update(int x, int k)
{
for (int i = x; i <= N; i += lowbit(i))
{
c[i] += k;
}
}
int query(int x)
{
int res = 0;
for (int i = x; i; i -= lowbit(i))
{
res += c[i];
}
return res;
}
int query_range(int l, int r)
{
return query(r) - query(l - 1);
}
struct Query
{
int id, n, m, a;
bool operator <(const Query &x)const
{
return x.a > a;
}
}q[MAXN];
arr ans;
int main()
{
pre();
int Q;
scanf("%d", &Q);
for (int i = 1; i <= Q; i++)
{
int n, m, a;
scanf("%d%d%d", &n, &m, &a);
q[i] = Query{i, n, m, a};
}
sort(q + 1, q + Q + 1);
for (int t = 1, i = 0; t <= Q; t++)
{
int a = q[t].a, val;
while ((val = f[i + 1].first) <= a)
{
int d = f[++i].second;
for (int k = 1; d * k <= N; k++)
{
update(d * k, val * mu[k]);
}
}
int id = q[t].id, n = q[t].n, m = q[t].m;
if (n > m)
{
swap(n, m);
}
for (int l = 1, r; l <= n; l = r + 1)
{
int k1 = n / l, k2 = m / l;
r = min(n / k1, m / k2);
ans[id] += k1 * k2 * query_range(l, r);
}
}
for (int i = 1; i <= Q; i++)
{
printf("%d\n", ans[i] & 2147483647);
}
return 0;
}