BZOJ 3529: [Sdoi2014]数表

Description
有一张 n×m 的数表,其第 i 行第 j 列(1 <= i <= n, 1 <= j <= m)的数值为
能同时整除 i 和 j 的所有自然数之和。给定 a , 计算数表中不大于 a 的数之和。
Input
输入包含多组数据。
输入的第一行一个整数Q表示测试点内的数据组数
接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。
1 &lt; = n . m &lt; = 1 0 5 , 1 &lt; = Q &lt; = 2 × 1 0 4 1 &lt; =n.m &lt; =10^5 , 1 &lt; =Q &lt; =2×10^4 1<=nm<=1051<=Q<=2×104
Output
对每组数据,输出一行一个整数,表示答案模2^31的值。
Sample Input
2
4 4 3
10 10 5
Sample Output
20
148

这题出得很刁钻……

预备知识(莫比乌斯反演)
思路:
莫比乌斯反演+树状数组维护。
1.推导公式。
2.理解树状数组的用处。
3.线性筛中的细节。
4.小优化。

1.推导公式

首先,我们设两个函数:
F ( a ) = ⌊   n / a ⌋ F(a)=\left\lfloor\ n/a \right\rfloor F(a)= n/a* ⌊   m / a ⌋ \left\lfloor\ m/a \right\rfloor  m/a
表示 a ∣ g c d ( i , j ) a|gcd(i,j) agcd(i,j)的方案数.
f ( a ) f(a) f(a)表示 a = g c d ( i , j ) a=gcd(i,j) a=gcd(i,j)的方案数。
显然, F ( a ) = ∑ a ∣ d f ( d ) F(a)=\sum_{a|d} f(d) F(a)=adf(d)

设n<=m,那么,我们忽略a的存在,总方案数就是
∑ d = 1 n ∑ i = 1 n ∑ j = 1 n f [ d ] ∗ [ g c d ( i , j ) = = d ] ( 这 是 b o o l 表 达 式 , 真 为 1 , 假 为 0 ) \sum_{d=1}^n \sum_{i=1}^n \sum_{j=1}^n f[d]*[gcd(i,j)==d](这是bool表达式,真为1,假为0) d=1ni=1nj=1nf[d][gcd(i,j)==d](bool10=
∑ d = 1 n ∑ i = 1 ⌊   n / d ⌋ ∑ j = 1 ⌊   m / d ⌋ f [ d ] ∗ [ g c d ( i , j ) = = 1 ] \sum_{d=1}^n \sum_{i=1}^{\left\lfloor\ n/d \right\rfloor} \sum_{j=1}^{\left\lfloor\ m/d \right\rfloor} f[d]*[gcd(i,j)==1] d=1ni=1 n/dj=1 m/df[d][gcd(i,j)==1](把d当作部分单位元或者说只有i为d的倍数且j为d的倍数时,才满足 d ∣ g c d ( i , j ) d|gcd(i,j) dgcdi,j)=
∑ d = 1 n f [ d ] ∗ ∑ i = 1 ⌊   n / d ⌋ ∑ j = 1 ⌊   m / d ⌋ ∑ k ∣ i 且 k ∣ j μ [ k ] \sum_{d=1}^n f[d]*\sum_{i=1}^{\left\lfloor\ n/d \right\rfloor} \sum_{j=1}^{\left\lfloor\ m/d \right\rfloor}\sum_{k|i且k|j}\mu[k] d=1nf[d]i=1 n/dj=1 m/dkikjμ[k]=
∑ d = 1 n f [ d ] ∗ ∑ k = 1 ⌊   n / d ⌋ μ [ k ] ∗ F [ k ∗ d ] \sum_{d=1}^n f[d]*\sum_{k=1}^{\left\lfloor\ n/d \right\rfloor}\mu[k]*F[k*d] d=1nf[d]k=1 n/dμ[k]F[kd]=
∑ d = 1 n f [ d ] ∗ ∑ d ∣ i n μ [ i / d ] ∗ F [ i ] \sum_{d=1}^n f[d]*\sum_{d|i}^n\mu[i/d]*F[i] d=1nf[d]dinμ[i/d]F[i](又把1当作单位元)=
(改变枚举顺序) ∑ i = 1 n F [ i ] ∑ d ∣ i f [ d ] ∗ μ [ i / d ] \sum_{i=1}^n F[i] \sum_{d|i} f[d]*\mu[i/d] i=1nF[i]dif[d]μ[i/d].
对于 f [ i ] f[i] f[i]我们可以用整除分块来减少枚举。

2.理解树状数组的用处

此时再来考虑a的影响。我们可以用离线处理数据,并让a非降序排列。
如果 f [ x ] &lt; = a f[x]&lt;=a f[x]<=a,那么就可以用x为 F [ i ] F[i] F[i]贡献系数。
还是为了减少枚举,我们边线性筛,边处理f,并以约数和从小到大排序f。
对于每个 f [ x ] &lt; = a f[x]&lt;=a f[x]<=a,我们令x去贡献系数,并用树状数组维护系数。

3.线性筛中的细节

对于线性筛,我们的任务是求 μ 和 f \mu和f μf,至于 μ \mu μ的求法这里不细讲(不会请翻预备知识),这里提一下f的求法。
方法一:时间复杂度 O ( N l n N ) ≈ O ( N l o g N ) O(NlnN)\approx O(NlogN) O(NlnN)O(NlogN)

for(int i=1;i<=10000000;i++)//Eratosthenes筛法
	for(int j=i;j<=10000000;j+=i)
		f[j]+=i;

方法二:时间复杂度 O ( N ) O(N) O(N)
根据约数和公式:若 x = p 1 c 1 ∗ p 2 c 2 ∗ … … p k c k x=p_1^{c_1}*p_2^{c_2}*……p_k^{c_k} x=p1c1p2c2pkck,
f ( x ) = ∏ i = 1 k ∑ j = 0 c i p i j f(x)=\prod_{i=1}^k\sum_{j=0}^{c_i}p_i^j f(x)=i=1kj=0cipij

那么我们可以实现简单继承:
p j ∣ i p_j|i pji时, 设 k = ∏ i ≠ y p i c i , f ( i ∗ p j ) = f ( k ) ∗ p y c y + 1 + f [ i ] = f ( k ) + f ( i ) ∗ p y 设k=\prod_{i\ne y}p_i^{c_i},f(i*p_j)=f(k)*p_y^{c_y+1}+f[i]=f(k)+f(i)*p_y k=i̸=ypici,f(ipj)=f(k)pycy+1+f[i]=f(k)+f(i)py(读者可自行证明,其实就是代入公式,这里不赘述)
否则, f [ i ∗ p y ] = f [ i ] ∗ f [ p y ] ( 约 数 和 是 积 性 函 数 , 读 者 亦 可 自 行 证 明 , 其 实 又 是 代 入 公 式 ) f[i*p_y]=f[i]*f[p_y](约数和是积性函数,读者亦可自行证明,其实又是代入公式) f[ipy]=f[i]f[py],)

4.一个小优化:

因为答案要 m o d   2 31 mod \ 2^{31} mod 231,我们就用int存答案,溢出其实是 m o d   2 32 mod \ 2^{32} mod 232(需要理解),所以不影响答案,只是答案为负就转为正数即可。

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define g getchar()
using namespace std;
typedef long long ll;
const int N=1e5+10;
const int M=2e4+10;
const ll mod=1LL<<31;

void qr(int &x)
{
	char c=g;x=0;bool v=0;
	while(!(('0'<=c&&c<='9')||c=='-'))c=g;
	if(c=='-')v=1,c=g;
	while('0'<=c&&c<='9')x=x*10+c-'0',c=g;
	if(v)x=-x;
}

void write(int x)
{
	if(x/10)write(x/10);
	putchar(x%10+'0');
}

struct node
{
	int n,m,a,id;
	bool operator <(node b)const{return a<b.a;}
}a[M];

struct pnode
{
	int s,id;
	bool operator <(pnode b)const{return s<b.s;}
}f[N];

int T,n,m,prime[N/10],tot,miu[N],mo[N],ans[M],maxn,tr[N];
//f[i]表示i的约数和,对于mo[i](mother),设i除去最小的质数的所有因子后得到k,mo[i]=f[k];
bool v[N];

void get_prime()
{
	miu[1]=f[1].s=1;
	for(int i=2;i<=maxn;i++)
	{
		if(!v[i])prime[++tot]=i,miu[i]=-1,mo[i]=1,f[i].s=i+1;
		for(int j=1,k;j<=tot&&(ll)i*prime[j]<=maxn;j++)
		{
			v[k=i*prime[j]]=1;
			if(i%prime[j]==0)
			{
				miu[k]=0;
				f[k].s=mo[i] + f[i].s*prime[j];
				mo[k]=mo[i];
				break;
			}
			else 
			{
				miu[k]=-miu[i];
				f[k].s=f[i].s*f[prime[j]].s;
				mo[k]=f[i].s;
			}
		}
	}
	for(int i=1;i<=maxn;i++)f[i].id=i;
	sort(f+1,f+maxn+1);
}

int lowbit(int x){return x&-x;}

void add(int x,int v)
{
	while(x<=maxn)
	{
		tr[x]+=v;
		x+=lowbit(x);
	}
}

void change(int i)
{
	int d=f[i].id,s=f[i].s;
	for(int j=1;j*d<=maxn;j++)if(miu[j])
		add(d*j,miu[j]*s);
}

int solve(int x)
{
	int tot=0;
	while(x>0)
	{
		tot+=tr[x];
		x-=lowbit(x);
	}
	return tot;
}

int main()
{
	qr(T);
	for(int i=1;i<=T;i++)
	{
		qr(a[i].n);qr(a[i].m);qr(a[i].a);a[i].id=i;
		if(a[i].n>a[i].m)swap(a[i].n,a[i].m);
		if(a[i].n>maxn)maxn=a[i].n;
	}
	sort(a+1,a+T+1);
	get_prime();
	for(int i=1,j=1;i<=T;i++)
	{
		while(f[j].s<=a[i].a&&j<=N-10)change(j++);
		int &sum=ans[a[i].id],n=a[i].n,m=a[i].m;
		sum=0;
		for(int l=1,r;l<=n;l=r+1)
		{
			r=min(n/(n/l),m/(m/l));
			sum+=(solve(r)-solve(l-1))*(n/l)*(m/l);
		}
		if(sum<0)sum+=mod;
	}
	for(int i=1;i<=T;i++)write(ans[i]),puts("");
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值