佩尔方程

第一类佩尔方程

定义:形如 x 2 − d y 2 = 1 x^2 - dy^2 = 1 x2dy2=1(d>1,且d不是完全平方数)
要求第一类佩尔方程的解都是正整数解,也即 ( x , y ) , x > 0 , y > 0 (x,y),x>0,y>0 (x,y),x>0,y>0

为什么要求d不是完全平方数呢?
我们假设d是完全平方数
x 2 − d y 2 = 1 x^2 - dy^2 = 1 x2dy2=1等价于 ( x + d ∗ y ) ( x − d ∗ y ) = 1 (x+\sqrt d*y)(x-\sqrt d*y) = 1 (x+d y)(xd y)=1
所以它的解只有可能是(1,0),(-1,0),不是正整数解。

为什么d>1呢?因为如果d<=1,解也不会是正整数解,自己证明试试看。

显然佩尔方程 x 2 − d y 2 = 1 x^2 - dy^2 = 1 x2dy2=1有无数多个解,那么如何求最小的正整数解

我们把式子化简一下就变成了, x = 1 + d y 2 x=\sqrt{1+dy{2}} x=1+dy2 ,我们从y=1开始枚举y,如果 1 + d y 2 \sqrt{1+dy{2}} 1+dy2 整数的话,此时的x,y就是最小的正整数,也就得到了最小正整数解

那么如何得到所有解呢,也就是说我们最小正整数解是(x1,y1),那么如何得到(x2,y2),(x3,y3),…?

这里给出通解的迭代公式
x n = x n − 1 x 1 + d y n − 1 y 1 x_n = x_{n-1}x_1 + dy_{n-1}y_1 xn=xn1x1+dyn1y1
y n = x n − 1 y 1 + y n − 1 x 1 y_n = x_{n-1}y_1 + y_{n-1}x_1 yn=xn1y1+yn1x1
这个通解公式怎么推导的呢?我们要知其然,而知其所以然

推导:我们令(x1,y1),(x2,y2)为方程的两个特解
x 1 2 − d y 1 2 = 1 x_1^2-dy_1^2=1 x12dy12=1----①
x 2 2 − d y 2 2 = 1 x_2^2-dy_2^2=1 x22dy22=1----②
两式想乘得到
( x 1 2 − d y 1 2 ) ∗ ( x 2 2 − d y 2 2 ) = 1 (x_1^2-dy_1^2)*(x_2^2-dy_2^2)=1 (x12dy12)(x22dy22)=1
展开得到
x 1 2 x 2 2 − d x 1 2 y 2 2 − d y 1 2 x 2 2 + d 2 y 1 2 y 2 2 = 1 x_1^2x_2^2-dx_1^2y_2^2-dy_1^2x_2^2+d^2y_1^2y_2^2=1 x12x22dx12y22dy12x22+d2y12y22=1
-> [ x 1 2 x 2 2 + d 2 y 1 2 y 2 2 ] − d [ d x 1 2 y 2 2 + y 1 2 x 2 2 ] = 1 [x_1^2x_2^2+d^2y_1^2y_2^2]-d[dx_1^2y_2^2+y_1^2x_2^2]=1 [x12x22+d2y12y22]d[dx12y22+y12x22]=1
-> [ x 1 2 x 2 2 + d 2 y 1 2 y 2 2 + 2 d x 1 x 2 y 1 y 2 ] − d [ d x 1 2 y 2 2 + y 1 2 x 2 2 + 2 x 1 x 2 y 1 y 2 ] = 1 [x_1^2x_2^2+d^2y_1^2y_2^2+2dx_1x_2y_1y_2]-d[dx_1^2y_2^2+y_1^2x_2^2+2x_1x_2y_1y_2]=1 [x12x22+d2y12y22+2dx1x2y1y2]d[dx12y22+y12x22+2x1x2y1y2]=1
-> ( x 1 x 2 − d y 1 y 2 ) 2 − d ( x 1 y 2 + x 2 y 1 ) 2 = 1 (x_1x_2-dy_1y_2)^2-d(x_1y_2+x_2y_1)^2=1 (x1x2dy1y2)2d(x1y2+x2y1)2=1
所以 x 3 = x 2 x 1 + d y 2 y 1 , y 3 = x 2 y 1 + x 1 y 2 x_3=x_2x_1+dy_2y_1,y_3=x_2y_1+x_1y_2 x3=x2x1+dy2y1,y3=x2y1+x1y2
我们把下标换为n就得到
x n = x n − 1 x 1 + d y n − 1 y 1 x_n = x_{n-1}x_1 + dy_{n-1}y_1 xn=xn1x1+dyn1y1
y n = x n − 1 y 1 + y n − 1 x 1 y_n = x_{n-1}y_1 + y_{n-1}x_1 yn=xn1y1+yn1x1

这样的话我们只要知道第一个特解 ( x 1 , y 1 ) (x1,y1) (x1,y1),和第n-1个特解 ( x n − 1 , y n − 1 ) (x_{n-1},y_{n-1}) (xn1,yn1),就能够求出第n个解,但是很明现如果暴力求第n个解的话,时间复杂度是O(n),如果n很大的话就会超时,我们这时候可以用矩阵快速幂logn的复杂度来就快速求取,第n个解。

步骤:

step1:先通过暴力找到最小正整数解,也就是第一组解(x1,y1),也有一种快速找最小正整数解的方法,叫做连分数法,感兴趣的小伙伴自行百度。

代码:

#include<bits/stdc++.h>
typedef long long ll;
void solve(ll &x,ll &y,ll d)
{
     y=1;
     for(y=1;;y++)
     {
      tempc = ceil(sqrt(d*y*y+1));
      tempf = floor(sqrt(d*y*y+1));
     if(tempc==tempf) break;//只有整数的ceil和floor相同,也就是向上取整和向下取整
     }
	 x=tempc;
	 return ; 
}

step2:根据迭代公式找到所有我们要找的解,也就是
x n = x n − 1 x 1 + d y n − 1 y 1 x_n = x_{n-1}x_1 + dy_{n-1}y_1 xn=xn1x1+dyn1y1
y n = x n − 1 y 1 + y n − 1 x 1 y_n = x_{n-1}y_1 + y_{n-1}x_1 yn=xn1y1+yn1x1

方法1:暴力迭代法
代码:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
void solve(ll &x,ll &y,ll d)
{
     ll tempc,tempf; 
     for(y=1;;y++)
     {
      tempc = ceil(sqrt(d*y*y+1));
      tempf = floor(sqrt(d*y*y+1));
     if(tempc==tempf) break;
     }
	 x=tempc;
	 return ; 
}
int main()
{
	ll x1,y1,d;
	cin>>d;//输入佩恩方程的d 
	solve(x1,y1,d);
	ll xxi=x1,yyi=y1;
	ll xi,yi;
	int n; 
	for(int i=2;i<=n;i++)//这里可以用于暴力求第n个解输入n就行 
	{
	   xi = xxi*x1+d*yyi*y1;
	   yi = xxi*y1+yyi*x1;
	   xxi = xi;
	   yyi = yi; 
	}
	cout<<xi<<' '<<yi<<endl;
	return 0;
}

方法2:矩阵快速幂迭代法
如果我们需要用矩阵快速幂来求的话,我们就需要把通解转化为矩阵的表达形式。

[ x n y n ] \begin{bmatrix} x_n\\ y_n \end{bmatrix} [xnyn]= [ x 1 d y 1 y 1 x 1 ] n − 1 \begin{bmatrix} x_1&dy_1 \\ y_1&x_1 \end{bmatrix}^{n-1} [x1y1dy1x1]n1 [ x 1 y 1 ] \begin{bmatrix} x_1\\ y_1 \end{bmatrix} [x1y1]
这样我们就可以用矩阵快速幂来求取方程的第n个解,时间复杂度是O(logn),矩阵快速幂就类似于普通的快速幂,只不过我们的底数是一个矩阵,而不是一个常数,如果不了解矩阵快速幂的,请出门右转(矩阵快速幂)。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll Maxn=10;
/*矩阵乘法*/ 
void Matrix_ksm(ll a[][Maxn],ll b[][Maxn],ll size)
{

	ll temp[Maxn][Maxn]={0};//注意一定要初始化为0 
	for(ll i=1;i<=size;i++)
	 for(ll j=1;j<=size;j++)
	  for(ll k=1;k<=size;k++)
	  {
	  	  temp[i][j]+=a[i][k]*b[k][j];
	  }
	  for(ll i=1;i<=size;i++)
	   for(ll j=1;j<=size;j++)
	   a[i][j]=temp[i][j];
	   
	  return ;
}
void solve(ll &x,ll &y,ll d)
{
     ll tempc,tempf; 
     for(y=1;;y++)
     {
      tempc = ceil(sqrt(d*y*y+1));
      tempf = floor(sqrt(d*y*y+1));
     if(tempc==tempf) break;
     }
	 x=tempc;
	 return ; 
}
int main()
{
	ll x1,y1;
	ll n,d;
	printf("请输入d:\n");
	cin>>d;
	solve(x1,y1,d);//我们先求第一个解,也就是最小正整数解 
//	cout<<x1<<' '<<y1<<'\n';
	
	printf("请输入要求的第n个矩阵,n=:\n");
	cin>>n;
	ll ans[Maxn][Maxn]={0};
    for(ll i=1;i<=Maxn;i++) ans[i][i]=1;
    ll dishu[Maxn][Maxn];
    dishu[1][1]=x1;
    dishu[1][2]=d*y1;
    dishu[2][1]=y1;
    dishu[2][2]=x1;
    ll zhishu=n-1;
    /*矩阵快速幂*/ 
    while(zhishu)
    {
    	if(zhishu&1)
    	{
    		zhishu--;
    		Matrix_ksm(ans,dishu,2);
		}
		else 
		{
			zhishu>>=1;
			Matrix_ksm(dishu,dishu,2);
		}
	}
    ll xn = ans[1][1]*x1+ans[1][2]*y1;
	ll yn = ans[2][1]*x1+ans[2][2]*y1;
	cout<<xn<<' '<<yn<<endl;
	return 0; 
}

第一类佩尔方程例题讲解

有句熟话说得好“光说不练假把式”,下面我们就来分析几道例题

Street Numbers
题目大意:找出n,m使得 1 + 2 + 3 + . . . . + ( n − 1 ) = ( n + 1 ) + ( n + 2 ) . . . . + m 1+2+3+....+(n-1)=(n+1)+(n+2)....+m 1+2+3+....+(n1)=(n+1)+(n+2)....+m,输出前十个解(n,m)。每个数字占是个字符,向右对齐,且每一排升序排列。

分析:我们不难发现,等式的左右都是一个等差数列,那么我们处理一下就可以得到这样一个等式, ( 2 m + 1 ) 2 − 8 n 2 = 1 (2m+1)^2-8n^2=1 (2m+1)28n2=1,我们令 x = ( 2 m + 1 ) , y = n , d = 8 x=(2m+1),y=n,d=8 x=(2m+1),y=n,d=8,这不就是一个裸的佩尔方程吗?我们只需要找到x,y就能找到m,n。

AC代码:

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
typedef long long ll;
/*找佩尔方程的第一个解,也就是正整数解*/
void solve(ll &x,ll &y,ll d)
{
     ll tempc,tempf; 
     for(y=1;;y++)
     {
      tempc = ceil(sqrt(d*y*y+1));
      tempf = floor(sqrt(d*y*y+1));
     if(tempc==tempf) break;
     }
	 x=tempc;
	 return ; 
}
int main()
{
	ll x1,y1,d=8;
	solve(x1,y1,d);
	ll xxi=x1,yyi=y1;
	ll xi,yi;
	/*通过迭代公式依次找到前十个解,注意这道题答案,第一个解不算在里面
	,所以前十个解是从第2到第11个解*/
	for(int i=2;i<=11;i++)//这里可以用于暴力求第n个解输入n就行 
	{
	   xi = xxi*x1+d*yyi*y1;
	   yi = xxi*y1+yyi*x1;
	   xxi = xi;
	   yyi = yi; 
	   printf("%10lld%10lld\n",yyi,(xxi-1)/2);/*注意题目要求每一个数字占
	   十个字符,每一排升序排列,所以我们先输出n,
	   再输出m,因为(n<m),*/
	}
	return 0;
} 

我们再来一道板子题No more tricks, Mr Nanguo

题目大意:就是一开始乐队有n个人,可以站成一个边长为x的正方形,现在走了一个人,把剩余的n-1个人分成d(这个d题目会给出)个边长为y的正方形站列,显然n有无穷多个解,对应的x,y也有无穷多组解,现在让你求第k(k题目会给出)小的正整数解x

这道题自己尝试着写一下,我就不写题解了,不过提醒一点,运算的时候注意取模,不然数据太大long long都可能超,由于k会比较大,所以不建议用暴力迭代法会超时,用矩阵快速幂来解决

AC代码:

#include<iostream>
#include<cmath>
#include<cstdio>
using namespace std;
typedef long long ll;
const int mod=8191;
const ll Maxn=10;
/*矩阵乘法*/ 
void Matrix_ksm(ll a[][Maxn],ll b[][Maxn],ll size)
{

	ll temp[Maxn][Maxn]={0};//注意一定要初始化为0 
	for(ll i=1;i<=size;i++)
	 for(ll j=1;j<=size;j++)
	  for(ll k=1;k<=size;k++)
	  {
	  	/*在矩阵运算的时候,我们把每一个可能超范围的地方都取模*/
	  	  temp[i][j]=(temp[i][j]%mod+a[i][k]%mod*b[k][j]%mod)%mod;
	  }
	  for(ll i=1;i<=size;i++)
	   for(ll j=1;j<=size;j++)
	   a[i][j]=temp[i][j];
	   
	  return ;
}
void solve(ll &x,ll &y,ll d)
{
     ll tempc,tempf; 
     for(y=1;;y++)
     {
      tempc = ceil(sqrt(d*y*y+1));
      tempf = floor(sqrt(d*y*y+1));
     if(tempc==tempf) break;
     }
	 x=tempc;
	 return ; 
}
int main()
{
	ll x1,y1;
	ll n,d;
	while(scanf("%lld %lld",&d,&n)!=EOF)
	{
	if(d<=1||ceil(sqrt(d))==floor(sqrt(d)))
	{
		cout<<"No answers can meet such conditions"<<'\n';
		continue;
	} 
	solve(x1,y1,d);//我们先求第一个解,也就是最小正整数解 
	ll ans[Maxn][Maxn]={0};
    for(ll i=1;i<=Maxn;i++) ans[i][i]=1;
    ll dishu[Maxn][Maxn];
    dishu[1][1]=x1;
    dishu[1][2]=d*y1;
    dishu[2][1]=y1;
    dishu[2][2]=x1;
    ll zhishu=n-1;
    /*矩阵快速幂*/ 
    while(zhishu)
    {
    	if(zhishu&1)
    	{
    		zhishu--;
    		Matrix_ksm(ans,dishu,2);
		}
		else 
		{
			zhishu>>=1;
			Matrix_ksm(dishu,dishu,2);
		}
	}
    ll xn = ans[1][1]%mod*x1%mod+ans[1][2]%mod*y1%mod;
	ll yn = ans[2][1]%mod*x1%mod+ans[2][2]%mod*y1%mod;
	cout<<xn%mod<<'\n';
     }
	return 0; 
}

第二类佩尔方程

定义:形如 x 2 − d y 2 = k x^2 - dy^2 = k x2dy2=k的方程叫做第二类佩恩方程
第二类佩尔方程可以看成是第一类佩尔的拓展,也相对复杂一点

解第二类佩恩方程,需要用到第一类方程的解。

推导:
我们假设 x 2 − d y 2 = k − − − − − ① x^2 - dy^2 = k-----① x2dy2=k的一个特解为 p , q p,q pq
x 2 − d y 2 = 1 − − − − − ② x^2 - dy^2 = 1-----② x2dy2=1的正整数解为 x 1 , y 1 x1,y1 x1,y1,①×②式就可以得到下面式子。
-> ( p 2 − d q 2 ) ∗ ( x 1 2 − d y 1 2 ) = k (p^2-dq^2)*(x_1^2-dy_1^2)=k (p2dq2)(x12dy12)=k
-> ( p x 1 ± d q y 1 ) 2 − d ( p y 1 ± q x 1 ) 2 = k (px_1\pm dqy_1)^2-d(py_1\pm qx_1)^2 =k (px1±dqy1)2d(py1±qx1)2=k
所以通解为, x = p x 1 ± d q y 1 x=px_1\pm dqy_1 x=px1±dqy1, y = p y 1 ± q x 1 y=py_1\pm qx_1 y=py1±qx1
我们假设②式中d=2,那么 x 2 − 2 y 2 = 1 x^2-2y^2=1 x22y2=1,解得最小正整数解是 x 1 = 3 , y 1 = 2 x_1=3,y_1=2 x1=3,y1=2,那么①式的解就是 x n = 3 x n − 1 ± 4 y n − 1 x_n=3x_{n-1}\pm 4y_{n-1} xn=3xn1±4yn1, y n = 2 x n − 1 ± 3 y n − 1 y_n=2x_{n-1}\pm 3y_{n-1} yn=2xn1±3yn1

遇到第二类佩尔方程也不用害怕,只需要和第一类佩尔方程联系起来求出通解即可。我们更具第二类佩尔方程的通解,
x = p x 1 + d q y 1 x=px_1+dqy_1 x=px1+dqy1, y = p y 1 + q x 1 y=py_1+ qx_1 y=py1+qx1,可以转化为矩阵乘法的形式
[ x n y n ] \begin{bmatrix} x_n\\ y_n \end{bmatrix} [xnyn]= [ p d q q p ] n − 1 \begin{bmatrix} p &dq \\ q&p \end{bmatrix}^{n-1} [pqdqp]n1 [ x 1 y 1 ] \begin{bmatrix} x_{1}\\ y_{1} \end{bmatrix} [x1y1],这样就方便用矩阵快速幂求第k个解

请关注我看更多数论分支知识体系讲解,也别忘点个赞额!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐