Comet OJ解不定方程-素数分解式+递归
题目描述:
小象同学在初等教育时期遇到了一个复杂的数学题,题目是这样的:
给定自然数 n,确定关于 x,y,z 的不定方程
的所有自然数解。
当时的小象同学并不会做这道题。多年后,经过高等教育的洗礼,小象同学发现这道题其实很简单。小象同学认为你一定也会做这道题,所以把这道题留给了你。为了便于输出,你不需要输出每一组解 (x,y,z),你只需要给出解的数量和所有解的 xyz之和对 (109+7)取模的值即可。注意,解的数量不对(109+7)取模。
输入描述:
输入包含多组测试数据。输入的第一行包含一个正整数 T (1≤T≤104),表示测试数据的组数。接下来依次描述每组测试数据,对于每组测试数据:
仅一行,包含一个非负整数 n(0≤n≤2×109),含义如题面所示。
输出描述:
对于每组数据,输出一行。若方程有无穷多组自然数解,则在这一行输出 “infty”(不含引号),否则在这一行输出两个整数,其中第一个整数表示方程的解数,第二个整数表示所有解的 xyz之和对 (109+7) 取模的值,这两个整数之间用恰好一个空格隔开,行末不要有多余的空格。
样例输入:
3
6
12
24
样例输出:
0 0
1 12
2 72
核心思路:
方程可化简为:将n/4进行素数分解,然后利用递归枚举y的值,细节见代码。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define mo 1000000007
using namespace std;
typedef long long ll;
const int N=1e5+30;
int pre[N],cnt;//筛出来的素数
bool vis[N];
ll p[N],nu,n,a1,a2;//p数组存素数因子,nu是p数组大小,a1,a2是要输出的俩数
int b[N];//b数组是p数组对应素因子的个数
void xss()//欧拉筛法
{
for(int i=2;i<N-10;i++)
{
if(!vis[i])
pre[cnt++]=i;
for(int j=0;j<cnt&&pre[j]*i<N-5;j++)
{
vis[pre[j]*i]=1;
if(i%pre[j]==0)break;
}
}
}
void fun(ll y,int k)//递归求每一种解
{
if(n/y<=y)//按照题意,z应该大于y
return;
if(k>=nu)//递归出口
{
a1++;
a2=(a2+n*(n/y+y))%mo;
return;
}
for(int i=0;i<=b[k];i++)//对于每个素因子,贡献情况有0,1,2....b[k]共b[k]+1种
{
fun(y,k+1);
y*=p[k];
}
return;
}
int main()
{
xss();
int T;
scanf("%d",&T);
while(T--)
{
nu=0;
a1=0;
a2=0;
scanf("%lld",&n);
double tt=n;
ll t=ll(sqrt(tt));
if(t*t==n||(t+1)*(t+1)==n)//如果n是一个自然数的平方,则有无数多种解,令x=根号n,y和z相等即可
{
printf("infty\n");
continue;
}
if(n%4!=0)//n不能被4整除,无解
{
printf("0 0\n");
continue;
}
n>>=2;//n除以4后,n=y*z
ll tn=n;
for(int i=0;i<cnt;i++)//求tn的素数分解式
{
if(tn==1)
break;
if(tn%pre[i]==0)
{
p[nu]=pre[i];
b[nu]=0;
while(tn%pre[i]==0)
{
b[nu]++;
tn/=pre[i];
}
nu++;
}
}
if(tn!=1)//如果1e5以内的素数无法将tn分解完全,则余下的tn是一个大于1e5的素数
{
p[nu]=tn;
b[nu]=1;
nu++;
}
fun(1,0);
printf("%lld %lld\n",a1,a2);
}
return 0;
}