NOIP2014提高组解方程

描述

已知多项式方程:

a0+a1x+a2x2+...+anxn=0

求这个方程在[1, m]内的整数解(n 和 m 均为正整数)。

格式

输入格式

输入共 n+2 行。

第一行包含 2 个整数 n、m,每两个整数之间用一个空格隔开。

接下来的 n+1 行每行包含一个整数,依次为 a0,a1,a2,...,an

输出格式

第一行输出方程在[1, m]内的整数解的个数。

接下来每行一个整数,按照从小到大的顺序依次输出方程在[1, m]内的一个整数解。

样例1

样例输入1[复制]

2 10
1
-2
1

样例输出1[复制]

1
1

样例2

样例输入2[复制]

2 10
2
-3
1

样例输出2[复制]

2
1
2

样例3

样例输入3[复制]

2 10
1
3
2

样例输出3[复制]

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分。

解题思路:使用秦九韶算法然后将【1m】内的值一一带入(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的上界的,应该有很大一部分被筛掉了。

解题思路:可以在【1m】范围内筛去不是A0约数的数i,及其相关数2i3i……,然后再用秦九韶算法处理方程左式,将剩余的数一一枚举带入即可。

(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过第khash的值,i表示几次方。将x带入方程计算sum的值,即方程左边的和,模素数是为了防止数太大。

if sum<>0 then//如果sum不是等于0说明x不是解

   begin

     i:=x;

      while i<=m do//同理x+hashx+2hashx+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); //读入nm

  for i:=0 to n do readdata(a[i]);//调用程序readdata读入各个系数

  for i:=1 to m do  //枚举解,从1枚举到m,虽然仍然是m个值,但是在程序checkcheck2里已经筛掉了部分数

    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<m的所有数字是不是解, 时间复杂度O(nk), 这里k需要自己选取. 之后对于1<=x<=m, 我们可以用已经算出来的x%k的结果,去很大概率估计x是不是解.有着更强正确率的方法是, 寻找若干个不同的k, 甚至最好选取k个大小相差不多的素数, 比如说我们选取10个k0,k1,...,k9.那么x是不是根就利用x%ki来判定.

(3)O(n^3+nsqrt(m))的算法, 我们选取素数p0,p1满足p0^2>=m,p1^2>m.然后在mod(pi)意义下算出来0<=x<pi的所有根, 这一部分的时间复杂度是O(nsqrt(m))的. 可以说明,找到的两组根都不超过n个,我们记为t1,t2,...,tu和s1,s2,...,sv. 那么我们断言原问题的可行根x一定可以通过某一个ti和某一个sj组合得到, 再利用O(n)的方法判定是不是根. 这一部分的时间复杂度是O(n^3), 这样可以通过所有的数据.

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值