已知多项式方程:
a0+a1x+a2x2+...+anxn=0
求这个方程在[1, m]内的整数解(n 和 m 均为正整数)。
输入共 n+2 行。
第一行包含 2 个整数 n、m,每两个整数之间用一个空格隔开。
接下来的 n+1 行每行包含一个整数,依次为a0,a1,a2,...,an
。
第一行输出方程在[1, m]内的整数解的个数。
接下来每行一个整数,按照从小到大的顺序依次输出方程在[1, m]内的一个整数解。
2 10 1 -2 1
1 1
2 10 2 -3 1
2 1 2
2 10 1 3 2
0
对于 30%的数据,0 < n ≤ 2, |ai|
≤ 100,an
≠ 0, m ≤ 100;
对于 50%的数据,0 < n ≤ 100, |ai|
≤ 10100
,an
≠ 0,m ≤ 100;
对于 70%的数据,0 < n ≤ 100, |ai|
≤ 1010000
,an
≠ 0,m ≤ 10000;
对于 100%的数据,0 < n ≤ 100, |ai|
≤ 1010000
,an
≠ 0,m ≤ 1000000。
解题思路:
首先,都可以意识到这道题的数据是相当庞大的,m ≤ 1000000。
如此一来,一道简单的解方程的题便不能用枚举法一一带入了,这种方法可谓是简单粗暴,然而并没有什么卵用,这样涉及到高精度乘高精度,高精度乘单精度,高精度加高精度和高精度减高精度。复杂度 n*m*len*len ,显然只能过30%的数据,故舍去。
所以在这种方法的基础上要考虑优化——
耗时大的方面主要是:带入数个x并且求值。还可以在开头加上一个艾森斯坦定理的判根法(不知道的自己百度)
(1)对于求值麻烦的问题:这里可以考虑秦九韶算法,将方程左边改写成如下形式:
这样一来便省去了高精×高精这一计算过程,复杂度大大下降,然而还是太复杂(复杂度为 n*len),即使也只能得到50分。
解题思路:使用秦九韶算法然后将【1,m】内的值一一带入(50分)
(2)求值的问题解决了但并不起多大作用,所以要考虑缩小取值的范围。(以下内容可以向学过数学奥赛的请教) 我们发现0的一个特征就是模任何一个数都是0,但我们多模几个质数是否能一定保证该数为0呢?当然不行。
那模谁呢?x。
显然,左边 mod x= A0 mod x ,所以 A0 mod x=0 ;
也就是说x一定是A0的约数!
然后我们的算法来了:
枚举1~m的每个数i,先判断它是不是A0的约数,这只要类似于高精除单精扫一遍就行了,如果不是,那么显然 2*i,3*i,……都不会是A0的约数,我们类似筛法筛掉这些。
如果是的话,带入求值判断是否为0.
当然如果A0=0的话,该方程的一个解是0,然后其余的解都满足 A1+A2*x^1+A3*x^2+……An*x^n-1=0,我们试A1即可。
当然,到这里,算法的复杂度还是不好估计,不过是不可能达到n*m*len的上界的,应该有很大一部分被筛掉了。
解题思路:可以在【1,m】范围内筛去不是A0约数的数i,及其相关数2i、3i……,然后再用秦九韶算法处理方程左式,将剩余的数一一枚举带入即可。
(3)取一个质数P,对[0,P−1]的整数进行验证(模P意义下),复杂度是O(Pn)。(注意挑选的P需要保证f(x)在模P意义下不会变成零多项式)然后可以知道模P意义下有不超过n个解。(拉格朗日定理)那么对应地,在[1,m]中至多只有n⋅⌈m/P⌉个解,对这些解进行验证即可。复杂度O(n2m/P)。
举个栗子:
equation.in |
equation.out |
2 10 1 -2 1 |
1 1 |
在这里我们得到的方程为:1-2x+x^2=0,取值在 [1,10]区间内。
取素数p,则(1/p)-(2/p)*x+(1/p)*x^2=0,依次将选择的素数带入,并且计算方程左式是否为0,若为0,此时的x就是方程的一组解,如果若干个素数都带入后仍不能满足方程,那么相应的x值以及与其相关的部分数可被筛去。
代码详解:
应用多次模素数并判断是否为解的方法来筛选[1,m]范围内的大量数据。(a 0/p )+(a1/p )x+(a2/p)x2+…+(an/p )xn=0,将数个素数及相应x带入求方程左式的和判断是否为0,若为0,则得到方程的一组解,否则该x及部分与x有关的数可被筛去(用布尔型做标记),都不可能成为方程的解,在筛去大部分数后,在主程序里可枚举【1,m】,此时完成解的记录与计数,输出即可。
Const
hash:array[1..10]of int64= (1007,10007,12347,12349,100017,111647,19720309,19750921,19981117,20150209); //选取几个较大的素数,也可以是其他数
type data=array[1..10]of int64;
var
a:array[0..100]of data;
list:array[1..1000000]of longint;//存取解的数组
n,m,i, k,x,top:longint; //n是最高次数,m是解的范围,i是数组a的下标,k是数组hash的下标,x是解,top是解的个数
ok:array[1..1000000]of boolean; //布尔型,在后面判断x是否为解
procedure readdata(var a:data);//这一部分是为了读入各个系数,要考虑符号问题,所以以字符的形式读入,再加以处理转化为所需系数
var c:char; //因为有负号且数据过大,所以只能选择字符型,且需要进行处理
b:boolean; //判断是否为“-”
i:longint; //计数
n:int64; //字符型转化的数字
begin
for i:=1 to 10 do a[i]:=0;
repeat
read(c);
until (c>='0')and(c<='9')or(c='-'); //读入字符直到不是空格
if c='-' then
begin
b:=true; //读入‘-’时做标记
read(c); //读入下一个字符
end
else b:=false; //读入字符型数字的标记
while (c>='0')and(c<='9') do//读入数字时进行处理
begin
n:=ord(c)-48; //将字符型c转化为数字n
for i:=1 to 10 do a[i]:=(a[i]*10+n)mod hash[i];
read(c); //读入下一个字符
end;
if b then//读入“-”时进行处理
for i:=1 to 10 do
if a[i]<>0 then
a[i]:=hash[i]-a[i];
end;
function check2(x:int64;k:longint):boolean;//k用来变化hash
var i:longint;
sum:int64;
begin
sum:=0;
for i:=n downto 0 do
sum:=(sum*x+a[i][k])mod hash[k];//a[i][k]表示mod过第k个hash的值,i表示几次方。将x带入方程计算sum的值,即方程左边的和,模素数是为了防止数太大。
if sum<>0 then//如果sum不是等于0说明x不是解
begin
i:=x;
while i<=m do//同理x+hash或x+2hash或x+3hash……也不是解
begin
ok[i]:=true;
i:=i+hash[k];
end; //计入x是不是方程的解
end;
exit(sum=0);
end;
function check(x:int64):boolean;//这个过程是判断x是否为方程的解
var k:longint;
begin
if ok[x] then exit(false);//如果x不是解则输出。
for k:=1 to 10 do
if check2(x,k)=false then exit(false);//如果有一个 mod hash 不成功,这个x就不是解。
exit(true);
end;
{============main============}
begin
readln(n,m); //读入n,m
for i:=0 to n do readdata(a[i]);//调用程序readdata读入各个系数
for i:=1 to m do //枚举解,从1枚举到m,虽然仍然是m个值,但是在程序check和check2里已经筛掉了部分数
if check(i) then
begin
inc(top);//若符合过程check则是方程的一组解,top+1
list[top]:=i; //同时将该解计入数组list
end;
writeln(top); //输出解的个数
for i:=1 to top do writeln(list[i]); //输出解
end.
其他解法:
(1) 先说一下O(nm)的算法.大家应该都知道可以把所有系数A_i都mod一个大素数, 之后在O(n)内判定一个数字x是不是解的方法了. 那么直接用这样的方法可以判定所有的数字, 时间复杂度是O(nm)的, 在noip现场应该可以得到期望得分70分.在加强版的题目中应该可以得到40分.
(2) 再说一下O(m)的算法,这个算法应该是可以在noip现场轻松通过所有数据的. 因为O(nm)会超时, 那么我们不妨只考虑1<=x<=K (3)O(n^3+nsqrt(m))的算法, 我们选取素数p0,p1满足p0^2>=m,p1^2>m.然后在mod(pi)意义下算出来0<=x