对于一个方程: x^2 + y^2 = z^2,他两两互质的正整数解满足一下条件
1. x = 2*m*n ,y = m^2 - n^2 ,z = m^2 + n^2
2. gcd( m , n) =1, m > n , m 与 n 的奇偶性不同
( 2*m*n )^2 + ( m^2 - n^2 )^2 = ( m^2 + n^2 )显然成立。
下面我们来看一个例题 POJ 1305 传送门
题意:
给定一个n,然后求满足上面那个方程的解的个数以及不满足勾股数的个数。
分析:
题目的数据范围比较小z^2的范围最大为1e6,因此我们可以直接根据奇偶性来
枚举。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 1e6+10;
int gcd(int a,int b){
if(b) return gcd(b,a%b);
return a;
}
int n;
bool vis[maxn];
void solve(){
memset(vis,0,sizeof(vis));
int ans1=0,ans2=0;
for(int i=1;i*i<=n;i++){
for(int j=2;j*j<=n;j+=2){
if(gcd(i,j)==1){
int l=i,r=j;
if(l>r) swap(l,r);
if(l*l+r*r<=n){
ans1++;
vis[2*r*l]=1;
vis[r*r-l*l]=1;
vis[l*l+r*r]=1;
}
int x = 2*l*r;
int y = -l*l+r*r;
int z = l*l+r*r;
for(int k=2;k*z<=n;k++)
vis[k*x]=1,vis[k*y]=1,vis[k*z]=1;
}
}
}
for(int i=1;i<=n;i++)
if(!vis[i]) ans2++;
printf("%d %d\n",ans1,ans2);
}
int main()
{
while(~scanf("%d",&n)){
solve();
}
return 0;
}
例题二:HDU3939 Sticks and Right Triangle 传送门
题意:
题意很简单就是求小于n的满足那个方程的解的个数,但是与上题不同的是这题的
数据范围比较大,l<=1e12,如果直接暴力求解的话肯定会超时。
分析:
首先我们先确定一下m,n的大致范围 m<=sqrt(l-1) n <= sqrt(l - m*m) = t。
然后我们还是只能枚举m.(m > n)我们分成以下两种情况来考虑。
1)m为偶数:
如果 m <= t 那么 n的可能选择就是 phi[m] 表示1~m中与m的互质的数的个数。
如果 m > t 那么 我们可以对m素因子分解,然后通过容斥原理计算1~t内与m
互质的数的个数也就是n的可能选择的方案数。
2)m为奇数:
这种情况下我们需要考虑一下我们需要的是奇偶性与m不相同的且与m互质的n的个
数。
如果 m <= t 那么 n的可能选择就是区间1~m/2内与m互质的数的个数。
如果 m > t 那么 n的可能选择就是 区间1~t/2内与m互质的数的个数。
因为1~m/2之内的与m互质的数只要乘一个2就转化到了区间1~m且仍与m互质。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long long LL;
const int maxn = 1e6+10;
int phi[maxn];
int prime[maxn],cnt,num;
LL ans ;
LL f[100];
bool vis[maxn];
void init(){//筛法求1e6以内的素数和欧拉函数
cnt = 0;
memset(vis,0,sizeof(vis));
for(int i=2;i<maxn;i++){
if(!vis[i]){
prime[cnt++]=i;
for(int j=i+i;j<maxn;j+=i) vis[j]=1;
}
}
for(int i=0;i<maxn;i++)phi[i]=i;
for(int i=2;i<maxn;i+=2) phi[i]>>=1;
for(int i=3;i<maxn;i+=2){
if(phi[i]==i){
for(int j=i;j<maxn;j+=i)
phi[j]=phi[j]-phi[j]/i;
}
}
}
void get_factor(int x){//对x进行素因子分解
num=0;
for(int i=0;i<cnt&&prime[i]*prime[i]<=x;i++){
if(x%prime[i]==0){
f[num++]=prime[i];
while(x%prime[i]==0) x=x/prime[i];
}
}
if(x>1) f[num++]=x;
}
void dfs(int id,int mul,int tot,int x){//容斥原理求[1,x]内与y互质的数的个数f[]为i的素因子
if(id==num){
if(tot&1) ans = ans - x/mul;
else ans = ans + x/mul;
return;
}
dfs(id+1,mul*f[id],tot+1,x);
dfs(id+1,mul,tot,x);
}
int main()
{
init();
int t;
scanf("%d",&t);
while(t--){
LL l;
scanf("%I64d",&l);
int n = sqrt(l+0.5);
ans = 0;
for(int i=1;i<=n;i++){//枚举m
int lim = sqrt(l-(LL)i*i+0.5);
if(i&1){
get_factor(i);
if(i<=lim) dfs(0,1,0,i>>1);
else dfs(0,1,0,lim>>1);
}
else{
if(i<=lim) ans=ans+phi[i];
else{
get_factor(i);
dfs(0,1,0,lim);
}
}
}
printf("%I64d\n",ans);
}
return 0;
}
这题的代码其实还可以优化,我们可以在预处理的时候将1e6之内的数都直接素因子分解
然后再后来调用的时候就可以 O(1)的查询了。