1.题意:给出递推公式Xi = (a*Xi-1 + b)%10001,给出T,x1,x3,x5.......x2T-1 , 让你输出可能的一组x2,x4,x6.....x2T
2.思路:
(1)暴力枚举a,b
(2)拓展欧几里得
S1:因为现在已知递推公式,T,x1,x3...
则 x2 = (a*x1 + b)%10001
x3 = (a*x2 + b)%10001
联立两个方程得:x3 = (a*(a*x1 + b)%10001 +b)%10001
可看出,如果我们已知a,b则可以由递推公式求出所有的xi,输出即可,那么怎么求出a,b,因为递推公式对10001取模,所以a,b取值范围为[0,10000],范围很小,为们可以枚举a,然后通过上述方程求出b,然后利用求出来的a,b去求所有的xi,当然如果利用这组a,b求出的x1,x3,x5与我们输入的不一样,也就是说明改组a,b只满足上述一个方程,并不能满足递推公式对应的输入。
(思路来源:计算机的重复性,计算机最擅长的就是重复工作,算法也是依据这个特性,所以暴力/枚举是最应该想到的方式!)
S2:如何解这个取模方程组
x3 = (a*(a*x1 + b)%10001 +b)%10001
=(a*(a*x1 + b) +b)%10001
=(a*a*x1 + b(a+1))%10001
所以x3 + 10001*k = a*a*x1 + b(a+1)
所以(a+1)b + 10001*(-k) = x3 - a*a*x1
类比方程ax + by = c的拓展欧几里得就可以解出b,利用a,b来求递推方程并验证结果
坑点:运算会爆int(2147483647十位),用longlong(2^63-1 19位)
3.代码:
#include <iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn = 200 + 7;
typedef long long int LL;
LL X[maxn];
LL ex_gcd(LL a,LL b,LL &x,LL &y){
if(b==0){ x = 1;y = 0;return a;}
LL gcd = ex_gcd(b,a%b,x,y);
LL temp = x;
x = y;
y = temp - a/b*y;
return gcd;
}
int main()
{
int T;
scanf("%d",&T);
for(int i = 1;i<=2*T;i+=2){
scanf("%lld",&X[i]);
}
for(int j = 0;j<=10000;j++){
int a = j+1;
LL Xp,Yp;
LL b = 10001;
LL c = X[3] - j*j*X[1];
LL gcd = ex_gcd(a,b,Xp,Yp);
if(c%gcd!=0)continue;
Xp = c/gcd*Xp;
//cout<<j<<" "<<Xp<<endl;
int temp = b/gcd;
Xp = (Xp%temp + temp)%temp;
bool flag = 1;
for(int i = 2;i<=2*T;i++){
LL num = (j*X[i-1] + Xp)%10001;
if(i&1){
if(X[i]!=num){flag = 0;break;}
}
else X[i] = num;
}
if(flag)break;
}
for(int i = 2;i<=2*T;i+=2){
printf("%lld\n",X[i]);
}
return 0;
}
//3
//17
//822
//3014