3529: [Sdoi2014]数表

3529: [Sdoi2014]数表

Time Limit: 10 Sec   Memory Limit: 512 MB
Submit: 1186   Solved: 604
[ 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

[ Submit][ Status][ Discuss]


HOME Back


莫比乌斯反演,,默认n <= m

令F[i] = i的所有约数和

题目要求的即是∑F[i] (F[i] <= a)

先忽略a的限制

换个思路,令f[i] = 以i为最大公约数的对数

ans = ∑f[i]*F[I] = ∑F[i]*∑u(d/i)*[n/d]*[m/d]   (i|d)

转换一下

ans = ∑[n/d]*[m/d]∑F[i]*u(d/i)   只要想办法处理好∑F[i]*u(d/i) 的前缀和,每次询问就能在根号n内完成

现在有了a的限制,,

先读入所有询问,按照a排序,我们需要的只有F[i] <= a的F

前缀和什么的,树状数组就行

每次要新增F的时候,根据公式,只需要维护i的每个倍数就行

至于F,用筛法O(nlogn),毕竟∑(n/i) = nlogn



嗯,,对∑的运算不够熟练,这个公式拆了好久,,GG

然后,第一次写的时候居然一边做一边取模,强行T一发

(明明把所有数字的约数合起来都不会爆long long啊)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
using namespace std;

typedef long long LL;
const int maxn = 1E5 + 10;
const LL mo = (1LL<<31LL);

struct data{
	LL F; int pos;
	bool operator < (const data &b) const {
		return F < b.F;
	}
}f[maxn];

struct Q{
	int n,m,a,num;
	Q(int _n = 0,int _m = 0,int _a = 0,int _num = 0) {
		n = _n; m = _m; a = _a; num = _num;
	}
	bool operator < (const Q &b) const {
		return a < b.a;
	}
}query[maxn];

int T,ans[maxn];
LL c[maxn],mu[maxn];
bool not_prime[maxn];

void Add(LL F,int pos)
{
	for (int i = 1; i*pos < maxn; i++) 
		for (int j = i*pos; j < maxn; j += j&-j)
			c[j] += F*mu[i];
}

LL sum(int pos)
{
	LL ret = 0;
	for (; pos > 0; pos -= pos&-pos)
		ret += c[pos];
	return ret;
}

int Work(LL n,LL m)
{
	int last,tail = min(n,m);
	LL ret = 0;
	for (LL i = 1; i <= tail; i = last + 1) {
		last = min(n/(n/i),m/(m/i));
		ret += (n/i)*(m/i)*(sum(last) - sum(i-1));
	}
	return ret%mo;
}

int main()
{
	#ifdef DMC
		   freopen("DMC.txt","r",stdin);
	#endif
	
	for (int i = 1; i < maxn; i++) mu[i] = 1;
	for (int i = 2; i < maxn; i++)
		if (!not_prime[i]) {
			mu[i] = -1;
			for (int j = 2; j*i < maxn; j++) {
				mu[j*i] *= mu[i];
				if (j % i == 0) mu[j*i] = 0;
				not_prime[j*i] = 1;
			}
		}
	for (int i = 1; i < maxn; i++) {
		f[i].pos = i;
		for (int j = 1; j*i < maxn; j++) f[j*i].F += 1LL*i;
	}
	sort(f + 1,f + maxn);
	cin >> T;
	for (int i = 1; i <= T; i++) {
		int x,y,z; scanf("%d%d%d",&x,&y,&z);
		query[i] = Q(x,y,z,i);
	}
	sort(query + 1,query + T + 1);
	
	int tail = 1;
	for (int i = 1; i <= T; i++) {
		while (tail < maxn && f[tail].F <= query[i].a) Add(f[tail].F,f[tail].pos),++tail;
		ans[query[i].num] = Work(query[i].n,query[i].m);
	}
	for (int i = 1; i <= T; i++) printf("%d\n",ans[i]);
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值