勾股数

勾股数

题目描述

我国是最早了解勾股定理的国家之一。早在三千多年前,周朝数学家商高就提出,将一根直尺折成一个直角,如果勾等于三、股等于四,那么弦就等于五,即“勾三、股四、弦五”。 (注:勾、股、弦分别指不等腰直角三角形中的较短直角边、较长直角边、斜边)它被记载于我国古代著名的数学著作《周髀算经》中。在这本书中的另一处,还记载了勾股定理的一般形式。
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 = ca,则化简可得:

    b2 = (c+a)(c-a )= (x+2a)x=x^2+2ax

    设y = bx,继续化简得到:

    a = y +y2/(2x)

    这意味着,确定了x,y,那么就可以得出a,b,c,且xy要满足一定条件。

   (1)xy有范围。(这个可以先写出a,b,c的表达式,然后解1≤a,b,cN

   (2)y2是2x的倍数。

    做法参考方法一,是类似的。此处不贴代码。    

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值