[HEOI2012]Akai的数学作业-题解
题目地址【IN】
- 题意简述
给你一个多项式方程,形式如下,求其所有的有理数解。
- 暴力
我们发现输出的是一个分子和分母互质的分数,所以我们枚举一下分子和分母,为了不超时,枚举大概不到,然后每次计算一下(用高精),大概能得到分。
- 优化
我们由于用高精,复杂度比较高,所以我们考虑取模意义下,当我们多取几个模数,在这几个模数的意义下,算出来都是,那么也可以看作是答案,但是有一定错的概率,当模数比较大且为质数时不容易错,大概能拿分。
- 分析
我们可以将开始的式子转换为分数形式,我们令
那么原式就可以写成:
接下来我们等式两端同时乘以,那么可以得到如下式子:
我们将式子两端同时模,那么可以得到:
我们可以得到肯定为的因子,因为互质,即。
我们再将原式两端同时模,那么同样可以得到:
我们同样可以的到肯定为的因子,同理,即
所以我们可得对于方程的解,我们就得知了且并且还要满足(也就是题面要求的互质)。
- 正解
有了上面的分析,我们就可以先预处理对于和的因子,但是这里有个问题就是如果或者,那么我们对于就换成左边第一个不为的系数,换成右边第一个不为的因子(由于系数是,所以前面的都可以相当于没有)。
然后我们可以枚举,由于的因子数最多为,所以我们可以直接枚举。
对于判断一个解是否合法,我们可以取几个模数,然后按照优化的暴力方式判断一个解是否合法(其实有一个非常好的模数,只需这一个,就可以判断了)。
然后对于每个枚举的,由于有负数的解,所以我们还要对每个枚举的去判断。
然后我们用一个结构体写一个分数类,将答案存入,并重载小于排序,输出答案即可。
下面上代码:
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int Mod=50033;
const int N=1010,M=110;
int gcd(int a,int b){return b?gcd(b,a%b):a;}
int fpow(int a,int b){int ans=1;for(;b;b>>=1,a=(1ll*a*a)%Mod)if(b&1)ans=(1ll*ans*a)%Mod;return ans;}
int in1[N],in2[N],c1,c2;
int A[M],n;
void init(){
int a=0,b=0;
for(int i=0;i<=n;i++)if(A[i]){a=A[i];break;}
for(int i=n;i>=0;i--)if(A[i]){b=A[i];break;}
if(a<0)a=-a;if(b<0)b=-b;
int t1=sqrt(a),t2=sqrt(b);
for(int i=1;i<=t1;i++){
if(!(a%i)){
in1[++c1]=i;
if(a/i!=i)in1[++c1]=a/i;
}
}
sort(in1+1,in1+c1+1);
for(int i=1;i<=t2;i++){
if(!(b%i)){
in2[++c2]=i;
if(b/i!=i)in2[++c2]=b/i;
}
}
sort(in2+1,in2+c2+1);
}
struct node{
int fz,fm;
node(){}
node(int a,int b):fz(a),fm(b){}
bool operator <(const node &a)const{
if((!fz||!fm)||(!a.fz||!a.fm)){
if((!fz||!fm)&&(!a.fz||!a.fm)) return 1;
if((!fz||!fm))return 0<a.fz;
if((!a.fz||!a.fm))return fz<0;
}
ll t1=1ll*fz*a.fm,t2=1ll*a.fz*fm;
return t1<t2;
}
void out(){
if(!fz||!fm) puts("0");
else if(fm==1) printf("%d\n",fz);
else{
int now=gcd(fz,fm);
fz/=now;fm/=now;
if(fm<0)fz=-fz,fm=-fm;
printf("%d/%d\n",fz,fm);
}
}
}anss[M];
int tot;
void calc(int type,int p,int q){
int ans=0,t1=1,t2=1,inv_q=fpow(q,Mod-2)%Mod;
if(type)p=-p;
for(int i=1;i<=n;i++)t1=(t1*q)%Mod;
for(int i=0;i<=n;i++){
ans=(ans+1ll*A[i]*t1%Mod*t2%Mod)%Mod;
t1=(1ll*t1*inv_q)%Mod;
t2=(1ll*t2*p)%Mod;
}
if(!ans){
anss[++tot]=node(p,q);
}else return;
}
int main(){
scanf("%d",&n);
for(int i=0;i<=n;i++)scanf("%d",&A[i]);
init();
for(int i=0;i<=n;i++)A[i]%=Mod;
for(int i=1;i<=c1;i++){
for(int j=1;j<=c2;j++){
if(gcd(in1[i],in2[j])==1){
calc(0,in1[i],in2[j]);
calc(1,in1[i],in2[j]);
}
}
}
if(A[0]==0)anss[++tot]=node(0,0);
sort(anss+1,anss+tot+1);
printf("%d\n",tot);
for(int i=1;i<=tot;i++)anss[i].out();
return 0;
}
下面为的讲解:
复数域下的一个关于的次多项式一定可以分解成个含的一次多项式相乘,即一定存在一种形如的表示,其中每个式子都会产生一个复数域下的根(当然,这些根有可能重复)。
我们只用考虑有理数根,所以可以把方程改写为的样子,其中是一个关于的多项式,包含了所有的非有理数根,剩下的部分就表示了所有的有理数根。令的常数项为,最高次项的系数为,则原方程的最高次项的系数,常数项,所以对于一个有理数解,为的因子,为的因子。
两两枚举因子,判断是否合法就行,注意还要枚举负因子。
注意,如果那么还有一个解为,由于系数可能为,所以我们需要人为的把系数不为的最低次项作为方程的首项,最高次项作为方程的末项,再用上面的枚举因子的方法做。