hdu5608 function (莫比乌斯反演+杜教筛)

题目链接

题意:根据式子

想出这题的做法后看了几个博客想验证一下,然而看了四五篇博客发现博主的做法都和我不一样...用自己的想法AC后,发现时间比大部分代码快,直接到rank2,然后略微优化了一下线性筛预处理部分(理论上预处理到n的2/3次方也就是1e6最优,不过这题似乎2e6最优。)
这是我AC后找的某一个博主写的题解,里面的第一种做法和我的比较接近,第二种则是绝大部分人的做法:博文链接
可能还是有人和我一样做的,但是懒得去一个个翻博客了= =

下面开始说我的做法吧题目给出的式子很容易想到莫比乌斯反演,令g(n)=n^{2}-3*n+2,则有g(n)=\sum_{d|n}f(d),则由莫比乌斯反演有f(n)=\sum_{d|n}g(d)*\mu(\frac{n}{d})

因此所求式子可转化为\sum_{i=1}^{n}\sum_{d|i}g(d)*\mu(\frac{i}{d}),再将这个式子转化一下,可变为\sum_{d=1}^{n}g(d)\sum_{i=1}^{\left \lfloor \frac{n}{d} \right \rfloor}\mu(i),这样后面部分其实就是莫比乌斯函数的前缀和,可以通过杜教筛求得,然后由数论分块可知,出现的莫比乌斯函数前缀和只有O(sqrt(n))个值,而前面部分就只需要每次求一块的和,和后面部分乘起来再累加即可。

(虽然写过两道杜教筛的题...但是并没有自己算过它的复杂度,所以这里的复杂度我只知道杜教筛O(n^{\frac{2}{3}}),数论分块O(\sqrt n),但是不会算。。。会算的盆友可以在评论里教教我orz(一些直接说复杂度是O(\sqrt n)的博文只说了分块的复杂度啊= =))

代码如下:

(暗戳戳秀一波)

#include<bits/stdc++.h>
using namespace std;
#define For(i,a,b) for(int i=a;i<=b;i++)
#define ll long long
const int maxn=2000000;
const int mod=1e9+7;
int prime[150000],num,miu[maxn+5];
bool vst[maxn+5];
inline ll qpow(ll a,ll b){
	ll res=1;
	while(b){
		if(b&1) res=res*a%mod;
		b>>=1;
		a=a*1ll*a%mod;
	}
	return res;
}
inline void Pre(){
  miu[1]=1;
  for (int i=2;i<=maxn;i++){
    if (!vst[i]) prime[++num]=i,miu[i]=-1;
    for (int j=1;j<=num && (ll)i*prime[j]<=maxn;j++){
      vst[i*prime[j]]=1;
      if (i%prime[j]==0){
		miu[i*prime[j]]=0;
		break;
      }
      miu[i*prime[j]]=miu[i]*miu[prime[j]];
    }
  }
  for (int i=1;i<=maxn;i++) miu[i]+=miu[i-1];
}
unordered_map<ll,int> S;
inline int Sum(ll n){
  if (n<=maxn) return miu[n];
  if (S.find(n)!=S.end()) return S[n];
  int tem=1; ll l,r;
  for (l=2;l*l<=n;l++) tem-=Sum(n/l);
  for (ll tt=n/l;l<=n;l=r+1,tt--){
    r=n/tt;
    tem-=(r-l+1)*Sum(tt);
  }
  return S[n]=tem;
}
int inv3;
inline int solve(ll n){
	ll l,r,tmp=0;
	for(l=1;l*l<=n;l++){
		if(l<=2)continue;
		tmp=(tmp+(l*l-3*l+2)*Sum(n/l))%mod;
	}
	for(ll tt=n/l;l<=n;l=r+1,tt--){
		r=n/tt;
		tmp=(tmp+((r*(r-1)%mod*(r-2)%mod*inv3)%mod-((l-1)*(l-2)%mod*(l-3)%mod*inv3)%mod)%mod*Sum(tt))%mod;
	}
	return (int)((tmp%mod+mod)%mod);
}
int t,n;
int main(){
	Pre();
	inv3=qpow(3,mod-2);
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		printf("%d\n",solve(n));
	}
	return 0;
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值