【BZOJ 3529】 [Sdoi2014]数表

3529: [Sdoi2014]数表

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 411   Solved: 245
[ Submit][ Status][ Discuss]

Description

    有一张N×m的数表,其第i行第j列(1 < =i < =礼,1 < =j < =m)的数值为
能同时整除i和j的所有自然数之和。给定a,计算数表中不大于a的数之和。

Input

    输入包含多组数据。
    输入的第一行一个整数Q表示测试点内的数据组数,接下来Q行,每行三个整数n,m,a(|a| < =10^9)描述一组数据。

Output

    对每组数据,输出一行一个整数,表示答案模2^31的值。

Sample Input

2
4 4 3
10 10 5

Sample Output

20
148

HINT

1 < =N.m < =10^5  , 1 < =Q < =2×10^4

Source


莫比乌斯反演+树状数组。


首先求出f[i]表示能整除i的所有自然数之和。


以下来自PoPoQQQ的ppt。(P20)




大概的思路是:

1.先不考虑a的限制:

【BZOJ 1101】求出g[i]表示:gcd(x,y)=i的(x,y)共有多少对,那么答案就是sigma(g[i]*f[i])


经过变形,最后的式子是:


前面一部分:好多连续的不同的d,n/d和m/d都是不变的,可以一起计算,时间为O(nsqrt(n))


后面一部分:在预处理的时候,枚举i的倍数,加到对应的d中;然后求出前缀和。预处理复杂度O(nsqrt(n))



但是,有了a的限制怎么办?


离线来做:

把询问按照a从小到大排序,f[]也从小到大排序,那么对于一个询问来说,要计算的是f[i]<=a的i。


使用树状数组维护sigma(f[i]*u(d/i))的前缀和,每次插入小于当前a的f[i](并嵌套上对于d的循环),然后询问前缀和即可。


#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdio>
#define M 100000
using namespace std;
pair<int,int> f[M+5];
int t[M+5],p[M],mu[M+5],ans[M+5],check[M+5],T;
struct Q
{
	int n,m,a,id;
}q[M];
bool cmp2(Q a,Q b)
{
	return a.a<b.a;
}
void Getmobius()
{
	mu[1]=1;
	int tot=0;
	for (int i=2;i<=M;i++)
	{
		if (!check[i])
		{
			p[++tot]=i;
			mu[i]=-1;
		}
		for (int j=1;j<=tot;j++)
		{
			if (i*p[j]>M) break;
			check[i*p[j]]=1;
			if (i%p[j]==0)
			{
				mu[i*p[j]]=0;
				break;
			}
			else mu[i*p[j]]=-mu[i];
		}
	}
}
void Getf()
{
	for (int i=1;i<=M;i++)
		for (int j=i;j<=M;j+=i)
			f[j].first+=i;
	for (int i=1;i<=M;i++)
		f[i].second=i;
	sort(f+1,f+1+M);
}
int lowbit(int x)
{
	return x&(-x);
}
void Update(int x,int k)
{
	for (int i=x;i<=M;i+=lowbit(i))
		t[i]+=k;
}
int Getsum(int x)
{
	int ans=0;
	for (int i=x;i;i-=lowbit(i))
		ans+=t[i];
	return ans;
}
int Query(int n,int m)
{
	int ans=0,pos;
	if (n>m) swap(n,m);
	for (int i=1;i<=n;i=pos+1)
	{
		pos=min(n/(n/i),m/(m/i));
		ans+=(n/i)*(m/i)*(Getsum(pos)-Getsum(i-1));
	}
	return ans&0x7fffffff;
}
int main()
{
        Getmobius();
	Getf();
	scanf("%d",&T);
	for (int i=1;i<=T;i++)
		scanf("%d%d%d",&q[i].n,&q[i].m,&q[i].a),q[i].id=i;
	sort(q+1,q+1+T,cmp2);
	int j,i;
	for (i=1,j=1;i<=T;i++)
	{
		for (;j<=M&&f[j].first<=q[i].a;j++)
			for (int k=f[j].second;k<=M;k+=f[j].second)
				Update(k,f[j].first*mu[k/f[j].second]);
		ans[q[i].id]=Query(q[i].n,q[i].m);
	}
	for (int i=1;i<=T;i++)
		printf("%d\n",ans[i]);
	return 0;
}



感悟:

莫比乌斯函数在使用时,要对sigma套sigma进行变形:如果后一个sigma中的某一个数与这个sigma的循环变量无关,那么可以提到前面去

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值