题目:给一个素数n(≤32767),一个整数满足gcd(a,n) = 1,求x使得x * x ≡ a (mod n). 数据可能高达100000组。
分析:网上有很详细的用二次剩余相关知识求解的方法,这里给一个不用那么高深的知识的方法(主要是因为自己数论知识不够...)。
首先经过分析:若x,y同为方程的解,则
x² ≡ y² (mod n)
故 (x + y)(x - y) ≡ 0 (mod n)
得到 x + y = n
也就是说:若x为同余方程的解,则n-x也是一个解(注意n=2的情况例外),并且也只可能有这两个解。
就用这些数论知识就够了,接下来看如何解题。
最朴素的方法,对于每一个方程,枚举从1到n/2的所有数,这当然会TLE。再结合题目给的数据范围,n不超过32767,在这个范围内总共大概有3000多个素数,是不是可以考虑打表?提前把所有素数以及对应的同余方程的解求出来,枚举次数为素数的和的一半,简单估算一下,相当于2.5 * 10⁷,这是可以接受的数据量!
时间问题就算解决了,问题在于打表的数据量太大,空间不够用了。解决办法为,对于n相同的数据,一次性处理完,这样每次只用记录一个素数对应的全部解,因此要先把所有待求数据读取并记录,然后把n相同的数据放到一起(排序),求解完毕后再把数据顺序还原(读取数据时记录id,按id排序即可还原顺序)。额外牺牲的时间为两次排序操作,这是可以接受的。最终通过时间为400ms。
//保存读取数据
struct Case
{
int a;
int p;
int id;
int x;
} cs[100000];
int x[32768];
bool cmp1(const Case &a, const Case &b)
{
return a.p < b.p;
}
bool cmp2(const Case &a, const Case &b)
{
return a.id < b.id;
}
int main(int argc, char *argv[])
{
int t, a, n;
cin >> t;
for (int i = 0; i < t; i++)
{
scanf("%d%d", &cs[i].a, &cs[i].p);
cs[i].a %= cs[i].p;
cs[i].id = i;
}
//按每组数据的素数大小排序
sort(cs, cs + t, cmp1);
for (int i = 0; i < t;)
{
int p = cs[i].p;
memset(x, 0, sizeof(int) * p);
for (int j = 1; j <= (p >> 1); j++)
{
x[j * j % p] = j;
}
//求解p相同的数据
while (i < t && cs[i].p == p)
{
cs[i].x = x[cs[i].a];
i++;
}
}
//还原数据顺序
sort(cs, cs + t, cmp2);
for (int i = 0; i < t; i++)
{
if (cs[i].x)
{
if (cs[i].p != 2) printf("%d %d\n", cs[i].x, cs[i].p - cs[i].x);
else puts("1");
}
else puts("No root");
}
return 0;
}