「洛谷P3768」简单的数学题 莫比乌斯反演+杜教筛

题目链接

简单的数学题

题目描述

输入一个整数n和一个整数p,你需要求出

i=1nj=1n(ijgcd(i,j)) mod p

其中gcd(a,b)表示ab的最大公约数

输入

一行两个整数p,n

输出

一行一个整数,为题目中所求值

样例

样例输入

998244353 2000

样例输出

883968974

数据范围

n1010
5×108p1.1×109
p为质数(但貌似也可以不是?又不用求逆元)

题解

自己想出来的题!但是连WA两发就是因为杜教筛写挂了……
先不考虑取余,我们化一下题目中的式子,枚举gcd(警告!多公式)。

i=1nj=1nijgcd(i,j)

d=1ndi=1ndj=1nd[ij]ijd2

d=1nd3i=1ndj=1nd[ij]ij

d=1nd3p=1ndμ(p)p2((1+ndp)ndp2)2

额,现在可以使用分块优化做到O(n)了,但是这完全不能胜任数据范围,我们换个角度,设dp=T,枚举T会有什么结果?

T=1n((1+nT)nT2)2d|Td3μ(Td)(Td)2

T=1n((1+nT)nT2)2T2d|Tdμ(Td)

现在好像反而变成O(nlogn)O(nn)了,别急,我们看看第二层的求和的意义——狄利克雷卷积,这是Id函数与μ函数的狄利克雷卷积,其值就等于φ

T=1n((1+nT)nT2)2T2φ(T)

现在,我们只需要快速求出一个东西即可——T2φ(T),前面的部分可以分块优化,我们急需解决的就是这个函数f(T)=T2φ(T)的前缀和F(T)。显然,这是一个积性函数。

杜教筛的公式:

i=1n(fg)(i)=i=1nd|if(d)g(id)=i=1ng(i)j=1nif(j)

于是我们需要一个函数与f卷起来,我们根据套路或枚举发现T2项很恼人,于是尝试把这一项消掉,于是想到了g(x)=x2

i=1nd|id2φ(d)(id)2=i=1ni2j=1nif(j)

i=1ni2d|iφ(d)=i=1ni2F(ni)

根据公式d|iφ(d)=i,继续变形

i=1ni3=F(n)+i=2ni2F(ni)

F(n)=i=1ni3i=2ni2F(ni)

由于p(i)=i3q(i)=i2的前缀和都有公式,我们可以对右边进行分块优化,就可以杜教筛了!这道题圆满解决,时间复杂度O(n23)

不过有些小细节要注意,比如模数乘2可能会爆intn2可能会爆long long,需要先取模再平方

Code:

#include <map>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 5000005
#define ll long long
map<ll, ll>Phi;
ll n, mod, g[N];
int p[N], h[N], phi[N], cnt;
ll sqr(ll x)
{
	ll a = 2 * x + 1, b = x + 1, c = x;
	if (b % 2 == 0)b /= 2;
	else c /= 2;
	if (a % 3 == 0)a /= 3;
	else
		if (b % 3 == 0)b /= 3;
		else c /= 3;
	a %= mod, b %= mod, c %= mod;
	return a * b % mod * c % mod;
}
ll seq(ll x)
{
	ll a = x + 1, b = x;
	if (a % 2 == 0)a /= 2;
	else b /= 2;
	a %= mod, b %= mod;
	return a * b % mod;
}
ll vas(ll x)
{
	ll a = seq(x);
	return a * a % mod;
}
ll G(ll x)
{
	if (x <= N - 5)
		return g[int(x)];
	if (Phi.find(x) != Phi.end())
		return Phi[x];
	ll ans = vas(x);
	ll lst = 1;
	for (ll i = 2; i <= x; i++)
	{
		i = x / (x / i);
		ll w = (sqr(i) - sqr(lst)) % mod;
		ans = (ans - w * G(x / i) % mod) % mod;
		lst = i;
	}
	if (ans < 0)
		ans += mod;
	Phi.insert(make_pair(x, ans));
	return ans;
}
ll Ans(ll x)
{
	ll ans = 0, lst = 0;
	for (ll i = 1; i <= x; i++)
	{
		i = x / (x / i);
		ll z = seq(x / i);
		z = z * z % mod;
		ans = (ans + z * (G(i) - G(lst)) % mod) % mod;
		lst = i;
	}
	if (ans < 0)
		ans += mod;
	return ans;
}
int main()
{
	phi[1] = 1;
	for (int i = 2; i <= N - 5; i++)
	{
		if (!h[i])
		{
			phi[i] = i - 1;
			p[++cnt] = i;
		}
		for (int j = 1; j <= cnt; j++)
		{
			if (i * p[j] > N - 5)
				break;
			h[i * p[j]] = 1;
			if (i % p[j] == 0)
				phi[i * p[j]] = phi[i] * p[j];
			else
				phi[i * p[j]] = phi[i] * (p[j] - 1);
		}
	}
	cin >> mod >> n;
	for (int i = 1; i <= N - 5; i++)
		g[i] = (g[i - 1] + 1ll * phi[i] * i % mod * i % mod) % mod;
	cout << Ans(n) << '\n';
}
posted @   ModestStarlight  阅读(477)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示