勾股数
题目描述
我国是最早了解勾股定理的国家之一。早在三千多年前,周朝数学家商高就提出,将一根直尺折成一个直角,如果勾等于三、股等于四,那么弦就等于五,即“勾三、股四、弦五”。 (注:勾、股、弦分别指不等腰直角三角形中的较短直角边、较长直角边、斜边)它被记载于我国古代著名的数学著作《周髀算经》中。在这本书中的另一处,还记载了勾股定理的一般形式。
1945年,人们在研究古巴比伦人遗留下的一块数学泥板(普林顿322号)时,惊讶地发现上面竟然刻有15组能构成直角三角形三边的数,其年代远在商高之前。
我们知道直角三角形两条直角边长a,b与斜边长之间满足等式:a²+ b² = c² 。我们满足这一等式的数组(a, b, c)称作勾股数组。给定正整数N,请你计算,对于1≤a≤b≤c≤N,能构成多少组勾股数组?
输入格式 2078.in
一个正整数N。
对于30%的测试数据,1≤N≤5 000;
对于70%的测试数据,1≤N≤50 000;
对于100%的测试数据,1≤N≤1 000 000 。
输出格式 2078.out
所求的答案。
输入样例 2078.in
25
输出样例 2078.out
8
解释: 共有8组勾股数组:(3,4,5),(6,8,10),(9,12,15),(12,16,20),(15,20,25),(5,12,13),(7,24,25),(8,15,17)。
普通人的普通想法(比如我),是枚举a, b,看a^2+b^2是否为完全平方数。
但是,这个限制关系不够强!所以做了很多无用的判断,造成超时。
因此,为了增加一些限制,我们可以进行公式变形,增加一些诸如因数等的限制。
变形方式一:
勾股定理可变形为:
a^2=c^2-b^2;
根据平方差公式,继续变形:
a^2=(c+b)(c-b);
我们设c-b为x,c+b为y。如果知道了x、y, 那么b、c也知道了。(不过b,c存在的前提是x,y奇偶性相同)
但是,假如我们暴力枚举a,嵌套枚举x, 时间复杂度仍是n^2。
那么,怎么优化呢?我们知道,其实每个数的因数个数都是很少的,也就是说,枚举x其实做了很多无用功。
那么,能不能直接得知a^2的因数呢?答案是可以。这一需求可以通过分解质因子得到实现。
例如:
108=2^2 * 3^3
我们枚举每种质因子取多少个,就可以得出数的所有的因数。(用DFS解决)
(另外,其实a每个质因子的指数乘2,就是a^2的质因数分解结果,所以只需分解a的质因数。)
如何分解质因数?我们可以按照将大问题变成小问题的思想。比如说,我们要对54进行质因数分解,它就是在27的基础上乘一个2;27又是在9的基础上乘上3。。。。。。这样下去,就能够求得原问题质因数分解的结果。
但是,上文中的子问题如何求得?实际上,子问题就是原数除以它最小质因子的结果。每个数的最小质因子,可以使用筛选法,在筛选的时候求得。
说完以上这些,整理一下操作流程吧:
筛选法(求各数最小质因子) O(NloglogN)
Fora=2 to N-1 O(N)
{
质因数分解 O(logN)
枚举因数(顺便计数) 硕小
}
总时间复杂度:O(NloglogN+NlogN)
代码如下:
#include<iostream>
#include<cstdio>
using namespace std;
const int MAXN=1e6+5;
int n,num,y,last;
int b[MAXN],c[MAXN],times[MAXN],ans;
void screen()
{
for(int i=2;i<=n;i++)
{
if(b[i]!=0) continue;
for(int j=1;j*i<=n;j++)
b[i*j]=i;//记录最小质因子
}
}
void run(long long x)
{
num=0;
y=x,last=-1;
while(y!=1)
{
if(b[y]!=last)
{
c[++num]=b[y];
times[num]=1;
}
else times[num]++;
last=b[y];
y/=b[y];
}
}
void dfs(int k,long long x,long long a)
{
if(x>=a) return;//小的那个
if(k>num)
{
long long y=a*a/x,b,c;
if(x%2!=y%2) return;
b=(y-x)/2;
c=(y+x)/2;
if(b>a&&b<=n&&c<=n) ans++;
return;
}
dfs(k+1,x,a);
long long now=1;
for(int i=1;i<=times[k];i++)
{
now*=c[k];
dfs(k+1,x*now,a);
}
}
int main()
{
cin>>n;
screen();
for(long long a=2;a<n;a++)
{
run(a);
for(int i=1;i<=num;i++) times[i]*=2;
dfs(1,1,a);
}
cout<<ans<<endl;
return 0;
}
总的来说,这题就是变形、变形、再变形。当直接求解一个问题,时间复杂度较大的时候,可以试图通过给它多一些明显的限制来优化。
另外,直接求解太慢,还要考虑间接求解。将大问题拆分成小问题,从递推的角度多考虑一下,或许就能找到答案。
变形方式二:
设x = c–a,则化简可得:
b2 = (c+a)(c-a )= (x+2a)x=x^2+2ax
设y = b–x,继续化简得到:
a = y +y2/(2x)
这意味着,确定了x,y,那么就可以得出a,b,c,且x和y要满足一定条件。
(1)x和y有范围。(这个可以先写出a,b,c的表达式,然后解1≤a,b,c≤N)
(2)y2是2x的倍数。
做法参考方法一,是类似的。此处不贴代码。