bzoj 3529

非常好的一道莫比乌斯反演题,对提升自己的能力有很大帮助。

首先我们分析一下题意:题意让我们求\sum_{k=1}\sigma(k)\sum_{i=1}^{n}\sum_{j=1}^{m}[gcd(i,j)==k],其中\sigma(k)\leq a

那么我们首先对后面的式子进行一下变形,变形过程详见https://blog.csdn.net/lleozhang/article/details/89093689

于是最后变成了这个样子:

\sum_{k=1}\sigma(k)\sum_{k|d}\mu(d/k)*[n/d]*[m/d]

但是这个式子还是不好,所以我们改变一下枚举顺序,先枚举d

于是上式改为:

\sum_{d=1}^{n}[n/d]*[m/d]\sum_{k|d}\sigma(k)*\mu(d/k)

可以发现右侧的式子是一个反演的形式,于是记f(d)=\sum_{k|d}\sigma(k)*\mu(d/k)

于是原式=\sum_{d=1}^{n}[n/d]*[m/d]*f(d)

先预处理出每个数的约数和(线性筛),然后利用数论分块+前缀和计算即可

等等,难道就这么简单?

当然不会了!!!

再仔细想想,我们上面的过程根本没有管a啊!

由于对k是有要求的,所以我们在预处理的时候如果不知道询问中的a,就没有办法确认对于每个d,哪个k是可用的!

那怎么办?

离线!

我们先将所有询问按照a从小到大排序,这样前面的询问所能用到的k后面的询问一定也可以用,这样我只需要每次将因为a变大而变得可用的k加入即可!

可是我们的要求是\sigma(k)\leq a,所以仅仅对a排序是不够的,由于约数和并不具有单调性,所以我们还要对约数和排序,然后对于每个询问,将新变得合法的k对应地累计进f(d)就可以了

由于这是一个动态的过程,使用树状数组维护f的前缀和,这样就既支持修改,又支持求前缀和了。

贴代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#define ll long long
#define mode (1ll<<31)
using namespace std;
struct Ques
{
	int n,m,a,num;
}q[100005];
int pri[100005];
bool used[100005];
int mu[100005];
struct SIG
{
	ll si;
	int num;
}sig[100005];
int mi[100005];
ll v[100005];
ll ret[100005];
int s[100005];
int cnt=0;
int lowbit(int x)
{
	return x&(-x);
}
void ins(int x,ll y)
{
	while(x<=100000)
	{
		s[x]+=y;
		x+=lowbit(x);
	}
}
int get_sum(int x)
{
	int ans=0;
	while(x)
	{
		ans+=s[x];
		x-=lowbit(x);
	}
	return ans;
}
ll pow_mul(ll x,int y)
{
	ll ans=1;
	while(y)
	{
		if(y&1)
		{
			ans*=(ll)x;
		}
		x*=x;
		y/=2;
	}
	return ans;
}
void init()
{
	mu[1]=1;
	sig[1].si=1;
	sig[1].num=1;
	for(int i=2;i<=100000;i++)
	{
		if(!used[i])
		{
			pri[++cnt]=i;
			mu[i]=-1;
			sig[i].si=i+1;
			sig[i].num=i;
			v[i]=i+1;
			mi[i]=1;
		}
		for(int j=1;j<=cnt&&i*pri[j]<=100000;j++)
		{
			used[i*pri[j]]=1;
			if(i%pri[j]==0)
			{
				mu[i*pri[j]]=0;
				ll temp=v[i]+pow_mul(pri[j],mi[i]+1);
				sig[i*pri[j]].si=sig[i].si/v[i]*temp;
				sig[i*pri[j]].num=i*pri[j];
				mi[i*pri[j]]=mi[i]+1;
				v[i*pri[j]]=temp;
				break;
			}
			mu[i*pri[j]]=-mu[i];
			sig[i*pri[j]].si=sig[i].si*(pri[j]+1);
			sig[i*pri[j]].num=i*pri[j];
			v[i*pri[j]]=pri[j]+1;
			mi[i*pri[j]]=1;
		}
	}
}
bool cmp(Ques x,Ques y)
{
	return x.a<y.a;
}
bool cmp1(SIG x,SIG y)
{
	return x.si<y.si;
}
int solve(int x,int y)
{
	if(x>y)
	{
		swap(x,y);
	}
	int ans=0;
	int last=0;
	for(int i=1;i<=x;i=last+1)
	{
		last=min(x/(x/i),y/(y/i));
		ans+=(get_sum(last)-get_sum(i-1))*(x/i)*(y/i);
	}
	return ans;
}
int T;
inline int read()
{
	int f=1,x=0;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
int main()
{
	T=read();
	init();
	int maxn=0;
	for(int i=1;i<=T;i++)
	{
		q[i].n=read(),q[i].m=read(),q[i].a=read();
		q[i].num=i;
		maxn=max(maxn,max(q[i].n,q[i].m));
	}
	sort(q+1,q+T+1,cmp);
	sort(sig+1,sig+100001,cmp1);
	int las=1;
	for(int i=1;i<=T;i++)
	{
		while(sig[las].si<=q[i].a&&las<=100000)//枚举k 
		{ 
			for(int j=1;j*sig[las].num<=maxn;j++)//枚举倍数 
			{
				ins(j*sig[las].num,sig[las].si*mu[j]);
			}
			las++;
		}
		ret[q[i].num]=solve(q[i].n,q[i].m)&2147483647;
	}
	for(int i=1;i<=T;i++)
	{
		printf("%d\n",ret[i]);
	}
	return 0;
}

 

posted @ 2019-04-09 18:50  lleozhang  Views(138)  Comments(0Edit  收藏  举报
levels of contents