『题解』Luogu-P3312 [SDOI2014]数表

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;
}
posted @ 2022-02-03 20:38  mango09  阅读(44)  评论(0编辑  收藏  举报
-->