HDU6706 CCPC 2019网络赛 huntian oy 推式子+杜教筛


CCPC 2019 网络赛 HDU 6706 huntian oy


标签

  • 奇奇怪怪的数论结论
  • 杜教筛

前言

  • 我的csdn和博客园是同步的,欢迎来访danzh-博客园~

简明题意

  • 给定n,a,b,求:
    ∑ i = 1 n ∑ j = 1 i g c d ( i a − j a , i b − j b ) [ g c d ( i , j ) = 1 ] % ( 1 0 9 + 7 ) \sum_{i=1}^n\sum_{j=1}^igcd(i^a-j^a,i^b-j^b)[gcd(i,j)=1]\%(10^9+7) i=1nj=1igcd(iaja,ibjb)[gcd(i,j)=1]%(109+7)

思路

  • 首先有一个结论:
    g c d ( i a − j a , i b − j b ) = i g c d ( a , b ) − j g c d ( a , b ) gcd(i^a-j^a,i^b-j^b)=i^{gcd(a,b)}-j^{gcd(a,b)} gcd(iaja,ibjb)=igcd(a,b)jgcd(a,b)
  • 上面的结论对于i,j互质是成立的。关注这题,条件式里就有[gcd(i,j)=1],所以我们可以直接替换:(由于ab互质,所以指数直接去掉)
    ∑ i = 1 n ∑ j = 1 i ( i − j ) [ g c d ( i , j ) = 1 ] \sum_{i=1}^n\sum_{j=1}^i(i-j)[gcd(i,j)=1] i=1nj=1i(ij)[gcd(i,j)=1]
  • (其实我比赛的时候猜出来gcd那一坨就等于i-j,然后我写了个暴力验证一下,发现有些数不相等,当时情急,就没往这方面想了,好难过)
  • 推到这里,就太简单了。接下来我们我们把减法分离开:
    ∑ i = 1 n ∑ j = 1 i i [ g c d ( i , j ) = 1 ] − ∑ i = 1 n ∑ j = 1 i j [ g c d ( i , j ) = 1 ] \sum_{i=1}^n\sum_{j=1}^ii[gcd(i,j)=1]-\sum_{i=1}^n\sum_{j=1}^ij[gcd(i,j)=1] i=1nj=1ii[gcd(i,j)=1]i=1nj=1ij[gcd(i,j)=1]
  • 然后分别求一下这两个式子。第一个式子比较好求,重点是第二个式子。
    ∑ i = 1 n ∑ j = 1 i j [ g c d ( i , j ) = 1 ] = 1 2 ∑ i = 1 n i ϕ ( i ) + [ n > = 1 ] 2 \sum_{i=1}^n\sum_{j=1}^ij[gcd(i,j)=1]=\frac 12\sum_{i=1}^ni\phi(i)+\frac{[n>=1]}{2} i=1nj=1ij[gcd(i,j)=1]=21i=1niϕ(i)+2[n>=1]

这个式子的推导过程放在我的博客《数论公式总结》里面了

  • 所以原式就等于
    ∑ i = 1 n i ϕ ( i ) − 1 2 ∑ i = 1 n i ϕ ( i ) + [ n > = 1 ] 2 = 1 2 ( ∑ i = 1 n i ϕ ( i ) − 1 ) \sum_{i=1}^ni\phi(i)-\frac 12\sum_{i=1}^ni\phi(i)+\frac{[n>=1]}{2}=\frac 12\left(\sum_{i=1}^ni\phi(i)-1\right) i=1niϕ(i)21i=1niϕ(i)+2[n>=1]=21(i=1niϕ(i)1)
  • 好了,n<=1e9,暴力去算和式会超时。这里杜教筛。
  • f ( n ) = n ϕ ( n ) f(n)=n\phi(n) f(n)=nϕ(n) S ( n ) = ∑ i = 1 n f ( i ) S(n)=\sum\limits_{i=1}^nf(i) S(n)=i=1nf(i)。我们杜教筛,构造g:
    S ( n ) g ( 1 ) = ∑ i = 1 n ( f ∗ g ) ( i ) − ∑ i = 2 n g ( i ) S ( n i ) S(n)g(1)=\sum_{i=1}^n(f*g)(i)-\sum_{i=2}^ng(i)S(\frac ni) S(n)g(1)=i=1n(fg)(i)i=2ng(i)S(in)
  • 现在难点就在于怎么找g函数。找g函数,我们一般先把狄利克雷展开:
    f ∗ g = ∑ d ∣ n d ϕ ( d ) ∗ g ( n d ) f*g=\sum_{d|n}d\phi(d)*g(\frac nd) fg=dndϕ(d)g(dn)
  • 所以很显然了,我们取g=id,卷积就是: f ∗ g = ∑ d ∣ n n ϕ ( d ) = n ∑ d ∣ n ϕ ( d ) f*g=\sum_{d|n}n\phi(d)=n\sum_{d|n}\phi(d) fg=dnnϕ(d)=ndnϕ(d)
  • ∑ d ∣ n ϕ ( d ) = n \sum\limits_{d|n}\phi(d)=n dnϕ(d)=n(这个相当于 ϕ ∗ I = i d \phi*I=id ϕI=id,用卷积很好证明),所以:
    ( f ∗ g ) ( n ) = ∑ d ∣ n n ϕ ( d ) = n 2 (f*g)(n)=\sum_{d|n}n\phi(d)=n^2 (fg)(n)=dnnϕ(d)=n2
  • 所以杜教筛的式子:
    S ( n ) = ∑ i = 1 n i 2 − ∑ i = 2 n i S ( n i ) S(n)=\sum_{i=1}^ni^2-\sum_{i=2}^niS(\frac ni) S(n)=i=1ni2i=2niS(in)
  • 然后就能很轻松地杜教筛啦~然而T了…
  • 上面的式子如果需要求积性函数的前缀和,那么大家肯定会写一个线性筛。而这里没有,是不是就代表不需要预处理呢?其实还是需要的,要预处理出 f ( n ) = n ϕ ( n ) f(n)=n\phi(n) f(n)=nϕ(n)的前缀和,才能降低杜教筛的复杂度。

注意事项

  • 注意溢出问题

总结

  • 杜教筛在分块的那一部分有减法操作,记得那里的减法操作不要写-=,因为那里的减法也要取模…

AC代码

#include
#include
using namespace std;

const int maxn = 5e6 + 10;
const int mod = 1e9 + 7;

int ksm(int a, int b)
{
	int ans = 1, base = a;
	while (b)
	{
		if (b & 1)
			ans = 1ll * ans * base % mod;
		b >>= 1;
		base = 1ll * base * base % mod;
	}
	return ans;
}

int inv6, inv2;

int prime[maxn], phi[maxn], pre[maxn];
bool no_prime[maxn];
int shai(int n)
{
	int cnt = 0;
	no_prime[1] = phi[1] = 1;

	for (int i = 2; i <= n; i++)
	{
		if (!no_prime[i])
			prime[++cnt] = i, phi[i] = i - 1;

		for (int j = 1; j <= cnt && i * prime[j] <= n; j++)
		{
			no_prime[prime[j] * i] = 1;
			phi[prime[j] * i] = i % prime[j] == 0 ? phi[i] * prime[j] : phi[i] * (prime[j] - 1);
			if (i % prime[j] == 0) break;
		}
	}

	for (int i = 1; i <= n; i++)
		pre[i] = (1ll * pre[i - 1] + 1ll * i * phi[i] % mod) % mod;

	return cnt;
}

unordered_map<int, int> rec;
int S(int n)
{
	if (n <= maxn - 10) return pre[n];
	if (rec[n]) return rec[n];
	long long ans = 1ll * n * (n + 1) % mod * (2 * n + 1) % mod * inv6 % mod;
	int l = 2, r = n;
	while (l <= n)
	{
		r = n / (n / l);
		ans = ((ans - 1ll * (l + r) * (r - l + 1) % mod * inv2 % mod * S(n / l) % mod) % mod + mod) % mod;
		l = r + 1;
	}
	return rec[n] = ans;
}

void solve()
{
	inv6 = ksm(6, mod - 2);
	inv2 = ksm(2, mod - 2);

	shai(maxn - 10);

	int t;
	scanf("%d", &t);
	while (t--)
	{
		int n, a, b;
		scanf("%d%d%d", &n, &a, &b);
		printf("%lld\n", 1ll * (S(n) - 1 + mod) % mod * inv2 % mod);
	}
}

int main()
{
	solve();
	return 0;
}

你可能感兴趣的:(#,推式子,#,杜教筛)