2018 ICPC 徐州網絡賽 D、Easy Math(推式子+杜教篩+枚舉因數+\mu性質)

求  \sum_{i=1}^m\mu(i*n) m<=2e9,n<=1e12


1、若  n 質因數分解後存在相同的質因子,那麼 \mu(n)=0,所以 gcd(i,n) \neq 1 的全部都是 0,並且函數 \mu(n) 是積性函數,所以 \sum_{i=1}^m\mu(i*n)=\sum_{i=1}^m\mu(i*n)[gcd(i,n)=1]=\sum_{i=1}^m\mu(i)*\mu(n)[gcd(i,n)=1]

2、考慮 e=\mu*I (迪利克雷卷積),展開得到 [n==1]=\sum_{d|n}\mu(d)*I(n/d)=\sum_{d|n}\mu(d),所以原式 =\sum_{i=1}^m\mu(i)*\mu(n)\sum_{d|\gcd(i,n)}\mu(d)

3、顯然 d|i,反向枚舉 d 得到式子 \mu(n)*\sum_{d|n}^{d<=m}\mu(d)\sum_{i=1,d|i}^m\mu(i)\ =\ \mu(n)*\sum_{d|n}^{d<=m}\mu(d)\sum_{i=1,}^{m/d}\mu(i*d)

4、發現這個式子和原式有點像,考慮設函數 F(n,m)=\sum_{i=1}^m\mu(i*n),可以得到 \sum_{i=1}^m\mu(i*n)=\mu(n)*\sum_{d|n}^{d<=m}\mu(d)\sum_{i=1}^{m/d}\mu(i*d),即 F(n,m)=\mu(n)*\sum_{d|n}^{d<=m}\mu(d)*F(d,m/d)

5、考慮臨界點,顯然 n=1 ,F(1,m)=\sum_{i=1}^m\mu(i),發現 m 的範圍有點大,每次用杜教篩處理一下就可以了

6、然後發現會 TLE,因爲在枚舉因數的時候 \sqrt n 的複雜度太高了,於是我們考慮 \mu 函數的性質,每個質因子只有 1 個,所以考慮採用枚舉質因子的方式枚舉出當前 n 的因數

#include <bits/stdc++.h>
#include <unordered_map>
#define ll long long //T了就換int 試試
#define sc scanf
#define pr printf
using namespace std;
const int MAXN = 6e6 + 5;//用板子前先改範圍

bool check[MAXN + 10];//值爲 false 表示素數,值爲 true 表示非素數
ll phi[MAXN + 10];//歐拉函數表
ll prime[MAXN + 10];//連續素數表
ll mu[MAXN + 10];//莫比烏斯函數
int tot;//素數的個數(從0開始
ll sub[MAXN + 10];
void jzk()
{
	//memset(check, false, sizeof(check));
	phi[1] = 1;
	mu[1] = 1;
	check[1] = true;
	tot = 0;
	for (int i = 2; i < MAXN; i++)
	{
		if (!check[i])
		{
			prime[tot++] = i;
			phi[i] = i - 1;
			mu[i] = -1;
		}
		for (int j = 0; j < tot; j++)
		{
			if (i * prime[j] >= MAXN)
				break;
			check[i * prime[j]] = true;
			if (i % prime[j] == 0)
			{
				phi[i * prime[j]] = phi[i] * prime[j];
				mu[i * prime[j]] = 0;
				break;
			}
			else
			{
				phi[i * prime[j]] = phi[i] * (prime[j] - 1);
				mu[i * prime[j]] = -mu[i];
			}
		}
	}
	for (int i = 1; i < MAXN; i++)
	{
		sub[i] = sub[i - 1] + mu[i];
	}
}

#define Pair pair<int,int>
unordered_map<ll, ll>mp;
map<Pair, ll>mps;
vector<ll>v;
bool get(ll n)//質因數分解並且判斷mu(n)是否等於0
{
	v.clear();
	for (int i = 0; i < tot && prime[i] * prime[i] <= n; i++)
	{
		if (n % prime[i] == 0)
		{
			int cnt = 0;
			while (n % prime[i] == 0)
			{
				n /= prime[i];
				cnt++;
			}
			v.push_back(prime[i]);
			if (cnt > 1)
				return true;
		}
	}
	if (n > 1)
		v.push_back(n);
	return false;
}
ll djsmu(ll n)//杜教篩求sub_mu
{
	if (n < MAXN)
		return sub[n];
	if (mp.count(n))
		return mp[n];
	ll ans = 0, j;
	for (ll i = 2; i <= n; i = j + 1)
	{
		j = n / (n / i);
		ans += (j - i + 1) * djsmu(n / i);
	}
	ans = 1 - ans;
	mp[n] = ans;
	return ans;
}
ll calc(ll m, ll n)
{
	if (get(n))//若 mu(n)==0,跳過
		return 0;
	if (mps.count(Pair{ m,n }))//記憶化
		return mps[Pair{ m,n }];
	if (n == 1)
		return djsmu(m);
	ll ans = 0, j;
	vector<ll>vv;
	for (int i = 0; i < v.size(); i++)//當前n一定是輸出n的因數,所以可以複用n的因數
	{
		if (n % v[i] == 0)
			vv.push_back(v[i]);
	}
	ll sz = vv.size();
	for (int i = 0; i < (1 << sz); i++)//由於mu(n)不等於0,所以每個質因數肯定只有一個
	{                                  //所以可以採用枚舉質因數的方式來湊出所有因數,防止超時
		ll d = 1, mud = 1;
		for (int j = 0; j < sz; j++)
		{
			if (i & (1 << j))
			{
				d *= vv[j];
				mud = -mud;
			}
		}
		if (d <= m)
			ans += mud * calc(m / d, d);
	}
	if (sz & 1)//mu(d) 的符號
		ans = -ans;
	mps[Pair{ m,n }] = ans;
	return ans;
}
int main()
{
	jzk();
	ll n, m;
	sc("%lld%lld", &m, &n);
	pr("%lld\n", calc(m, n));
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章