bzoj3944 sum 杜教筛

题目链接:传送门

前置技能:

莫比乌斯反演 & 狄利克雷卷积
杜教筛基础

解题思路:

点亮前置技能就珂以发现这是裸题……
首先杜教筛要卷积一个函数。
先考虑 S ( n ) = Σ i = 1 n φ ( i ) S(n)=\Large\Sigma\large_{i=1}^{n}φ(i) S(n)=Σi=1nφ(i)的情况:
发现有一条式子叫做 φ ∗ I = i d φ*I=id φI=id
所以让两边卷积一个 I I I,得到 ( I ∗ S ) ( n ) = Σ i = 1 n i d ( i ) = n ∗ ( n + 1 ) 2 (I*S)(n)=\Large\Sigma\large_{i=1}^{n}id(i)=\frac{n*(n+1)}{2} (IS)(n)=Σi=1nid(i)=2n(n+1)
按照杜教筛的套路乱搞一波(此处可直接套杜教筛的结论),得到:
S ( n ) = Σ i = 1 n ( I ∗ φ ) ( i ) − Σ i = 2 n I ( i ) S ( n i ) S(n)=\Large\Sigma\large_{i=1}^n(I*φ)(i)-\Large\Sigma\large_{i=2}^nI(i)S(\frac{n}{i}) S(n)=Σi=1n(Iφ)(i)Σi=2nI(i)S(in)
= n ∗ ( n + 1 ) 2 − Σ i = 2 n S ( n i ) =\large\frac{n*(n+1)}{2}-\Large\Sigma\large_{i=2}^nS(\frac{n}{i}) =2n(n+1)Σi=2nS(in)
然后整除分块+记忆化乱搞即可。

对于 S ( n ) = Σ i = 1 n μ ( i ) S(n)=\Large\Sigma\large_{i=1}^{n}μ(i) S(n)=Σi=1nμ(i)的情况,也类似地套模板即可。

奇技淫巧

发现递归的时候 n n n很大,记忆化不太好搞,这里需要map奇技淫巧来解决。
递归时需要记忆化的值只有 ⌊ n 2 ⌋ , ⌊ n 3 ⌋ , . . . . \lfloor\frac{n}{2}\rfloor,\lfloor\frac{n}{3}\rfloor,.... 2n,3n,....(这个先不证明)
因为当递归传递的值小于 n 2 3 n^{\frac{2}{3}} n32(一般设为 1 e 6 1e6 1e6左右)时,是直接线性筛出来的,所以珂以考虑对于 x x x,把需要记忆化的 v a l val val存在 ⌊ n x ⌋ \lfloor\frac{n}{x}\rfloor xn的位置。
因为 x > 1 0 6 x>10^6 x>106(否则调用线性筛的结果直接返回), n n n 1 0 10 10^{10} 1010左右,所以 ⌊ n x ⌋ \lfloor\frac{n}{x}\rfloor xn 1 0 4 10^4 104以下。
这样就珂以不用开一个 1 0 10 10^{10} 1010的数组或是map来记忆化了。
这里放一张图:
bzoj3944 sum 杜教筛_第1张图片

但是这样会不会冲突呢?比如 ⌊ ⌊ n 2 ⌋ 3 ⌋ \Large\lfloor\frac{\lfloor\frac{n}{2}\rfloor}{3}\rfloor 32n会不会与 ⌊ n 6 ⌋ \large\lfloor\frac{n}{6}\rfloor 6n不相等,导致需要记忆化的位置不只有 ⌊ n x ⌋ \lfloor\frac{n}{x}\rfloor xn呢?
这里需要引进一条玄学定理,叫做 ⌊ ⌊ n a ⌋ b ⌋ = ⌊ n a b ⌋ \Large\lfloor\frac{\lfloor\frac{n}{a}\rfloor}{b}\rfloor=\lfloor\frac{n}{ab}\rfloor ban=abn
这里就不证明了,珂以看OIWiki-莫比乌斯反演中的证明qwq
根据引理珂以看出不存在 ⌊ n x ⌋ ( x ≤ n ) \lfloor\frac{n}{x}\rfloor(x\le n) xn(xn)之外需要记忆化的位置。
所以如上乱搞即可qwq

另外,这题卡常数,要把两个前缀和在一个函数里面求出来。

update:
今天刚发现这个优化方法有适用范围:
如果对于很多个 x x x,都要求出 S ( x ) S(x) S(x)的值,就不适用了qwq
因为每次存储的位置是 ⌊ n i ⌋ \lfloor\frac{n}{i}\rfloor in,和 n n n有关
如果每次都强行用这种优化方法,每次要memset一下,时间复杂度就爆了qwq
举个栗子:洛谷P3172 CQOI2015 选数,即bzoj3930 CQOI2015 选数

毒瘤代码

#include
#include
#include
#include
#define re register int
#define mod 1000000000
using namespace std;
typedef long long ll;
int read() {
	re x=0,f=1;
	char ch=getchar();
	while(ch<'0' || ch>'9') {
		if(ch=='-')	f=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9') {
		x=10*x+ch-'0';
		ch=getchar();
	}
	return x*f;
}
const int Size=2000005;
ll tot,mu[Size],phi[Size],prime[Size];
ll summu[Size],sumphi[Size];
bool vis[Size];
void getp(int maxn) {
	phi[1]=mu[1]=1;
	phi[0]=0;
	for(re i=2; i<=maxn; i++) {
		if(!vis[i]) {
			prime[++tot]=i;
			mu[i]=-1;
			phi[i]=i-1;
		}
		for(re j=1; j<=tot; j++) {
			int now=i*prime[j];
			if(now>maxn)	break;
			vis[now]=true;
			if(i%prime[j]==0) {
				mu[now]=0;
				phi[now]=phi[i]*prime[j];
				break;
			}
			phi[now]=phi[i]*phi[prime[j]];
			mu[now]=-mu[i];
		}
	}
	for(re i=1; i<=maxn; i++) {
		summu[i]=summu[i-1]+mu[i];
		sumphi[i]=sumphi[i-1]+phi[i];
	}
}
ll n,m1[Size],m2[Size];
bool vis1[Size],vis2[Size];
pair<ll,ll> getans(ll x) {
	if(x<=2000000)	return make_pair(sumphi[x],summu[x]);
	int t=n/x;
	if(vis1[t])	return make_pair(m1[t],m2[t]);
	ll sum1=0,sum2=0,lst;
	for(ll i=2; i<=x; i=lst+1) {
		lst=x/(x/i);
		pair<ll,ll> tmp=getans(x/i);
		sum1+=(lst-i+1)*tmp.first;
		sum2+=(lst-i+1)*tmp.second;
	}
	vis1[t]=true;
	m1[t]=(x*(x+1ll)>>1ll)-sum1;
	m2[t]=1ll-sum2;
	return make_pair(m1[t],m2[t]);
}
int main() {
	getp(2000000);
	int T=read();
	while(T--) {
		n=read();
		memset(vis1,0,sizeof(vis1));
		memset(vis2,0,sizeof(vis2));
		pair<ll,ll> ans=getans(n);
		printf("%lld %lld\n",ans.first,ans.second);
	}
	return 0;
}
/*
1
2147483647
*/

你可能感兴趣的:(bzoj题目)