【数论】HDU6211 - Pythagoras

本文介绍了如何使用数论方法解决ACM算法竞赛中的问题,涉及如何判断两数奇偶性、求质因数、计算模2^n、判断互质性以及高效计算特定数列。通过预先处理和打表,解决了枚举和计算的大数问题,适用于计算不超过1e9的勾股数三元组。
摘要由CSDN通过智能技术生成

题意

给出k和a_{0}a_{1}...a_{2^{^{k-1}}} 这2^{^{k-1}}个数,求所有勾股数三元组(x, y, z) (x<y<z)中,求\sum a_{y \, mod \, 2^{k}}。注:x, y, z为正整数且小于等于1e9。

思路

不得不说我是完完全全的数论菜鸟,只会孙子定理的那种233。这道题真的是让我学会了好多东西。

在看思路之前,你需要了解如下知识。

1.如何快速判断两数是否一奇一偶

假设两数为a和b。则用一个表达式

(a-b)&1

即可完成。由于采用了位运算,所以效率很高。

原理:两数奇偶性相同,相减为偶数。两数奇偶性不同,相减为奇数。故两数做减法。&运算符为按位与,奇数的最低为是1,偶数是0。负数的补码一样适用这个规律。(自己开个计算器就懂了)

2.如何求一个数的所有质因数

话不多说,直接上代码。代码中m为被求,数组p里存访m的所有质因数。

for (int q=2; q*q<=m; q++) {
	if (!(m%q)) {
		p[cnt++] = q;
		while (!(m%q)) m/=q; //注意这里
	}
}
if (m != 1) p[cnt++] = m;

注意while语句。这种操作可以用O(logn)的时间复杂度消去m的所有已经算出的质因子。比如说64,找出了质因子2之后,m就立即变成1了。当然之后要“捡漏”。

3.如何快速算出a mod 2^n

这里采用位运算。

t&MOD

直接一个表达式解决所有问题。其中MOD为2的n次方减1。采用位运算可以写成

int MOD = (1 << n) - 1;

(不懂的话自己开个计算器秒懂233)

4.如何快速判断两个正整数互质

两个数互质,就是两个数没有大于1的公因子。只要判断前一个数是否能整除后一个数所有的质因子即可,反之亦然。这里就不上代码了。

5.如何快速计算形如a+i*i (i为正整数)的表达式

我们很容易想到如下方法。

for (int i=1; i<=n; i++) {
    int m = a + i*i;
}

如下方法也能达到同样的效果。

int c = a;
for (int j=1; j<i; j++) {
	c += j+j-1;
}

这种方法把多次运算的乘法,换成了累加。至于原理,大家算一遍就会明白了。优化常数阶。

------------------------------------------------华丽的分割线------------------------------------------------

这里采用预先处理打表的方法。

因为勾股数公式为x^2 + y^2 = z^2,这里z小于1e9(题目要求),所以可得x^2小于1e9(因为勾股数前两个一定小于最后一个)。

勾股数生成公式:x=i*i-j*j, y=2*i*j, z=i*i+j*j 其中i和j为互质且奇偶性相反的正整数。

所以枚举i从1到\sqrt{10^{9}},接着计算x的所有质因数。接着从1到i枚举j,这里有两个剪枝:第一个是判断勾股数第三个数z是否大于1e9。第二个为判断奇偶性是否相反。

接着判断i和j是否互质,如果是的话,就把i*i-j*j和2*i*j也就是x和y中较大的那个加入备选数组,使数组自增1。

等等,这里数可能很大(到1e9),数组开不下。这里又用到了一个优化:题目总数据量只有2的17次方,为131072。那我们先把这个数模2^17,加入数组即可。这样做的话,因为题目的实际范围使小于等于2的17次方的,比如说a mod 2^17 mod 2^4,结果和a mod 2^4是相同的。(还是开一下计算器就明白了,要用二进制的思想哦)正好还免去了重复打表的过程,一举两得。

以上是打表过程,需要预先操作。然后读入数据,此时备选数组中的内容就是次数。所以计算

long long sum = 0;
for (int i=0; i<=(1<<17)-1; i++) {
    sum += (long long)a[i&((1<<k)-1)]*b[i]
}

,其中a为题目所给数据,b为备选数组,累加即可,注意long long防止越界。

碎碎念:终于写完了,也是半夜进入新的一天了,明天还要上课,滚去睡觉了。

代码:

#include <cstdio>
#include <cmath>
typedef long long LL;
const int MAXN = (1 << 17);
const int MOD = (1 << 17) - 1;

int A[MAXN+5];
int K;

int B[MAXN+5];

void pre() {
	int sqrt1e9 = sqrt(1000000000);
	for (int i=1; i<=sqrt1e9; i++) {
		int m=i;
		int p[20];
		int cnt = 0;
		
		//计算质因数 
		int q;
		for (q=2; q*q<=m; q++) {
			if (!(m%q)) {
				p[cnt++] = q;
				while (!(m%q)) m/=q;
			}
		}
		if (m != 1) p[cnt++] = m;
		
		int c = i*i;
		for (int j=1; j<i; j++) {
			c += j+j-1;
			if (c>1000000000) break;	
			if (!((i-j)&1)) continue; //判断奇偶性 
				
			int k;
			for (k=0; k<cnt; k++) {
				if (!(j%p[k])) break;
			}
			
			if (k >= cnt) {
				int t = 2*i*j;
				if (i*i-j*j > t) t = i*i-j*j;
				B[t&MOD]++;
			}
		}
	}
}

void solve() {
	int t = (1 << K) - 1;
	LL sum = 0;
	for (int i=0; i<=MOD; i++) {
		sum += (LL)A[i&t]*B[i];
	}
	printf("%lld\n", sum);
}

int main() {
	pre();
	int T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d", &K);
		for (int i=0; i<(1 << K); i++) {
			scanf("%d", &A[i]);
		}
		solve();
	}
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值