NOIp 2014 解方程 【数学/秦九韶算法/大数取膜】By cellur925

 

题目传送门

题意:求高次方程的解及其个数。其中

 

我们知道,高次方程是没有求根公式的。但是利用逆向思维,我们可以进行“试根法”,因为题目中给出了所求根的范围。但是多项式系数过于吓人,达到了sxbk的1e10000.longlong显然盛不下。只能看做字符串处理。然而即使是处理成字符串,我们也不可能真的去乘这么多。

考虑取膜。我们把多项式系数进行取膜,它的相对效果和不取膜是一样的。(想一想,为什么)

除了对系数取膜,我们还可以考虑对x取膜。

- 如果 X 真的是一个根,那么取模后肯定是 0;但反之则是不确定。
- 逆否命题:如果取模之后不是 0,那么 X 肯定不是一个根。
- 我们就利用上面的这条性质来判断即可。

- 考虑一个较小的模数 p
- 如果对于 0<x<p 来说,代入 x 计算后不是 0,则那么对于 x+p
x+2px+3p...,这些数代入计算后都不可能为 0
- 所以我们只需要验证[1,p)之间的数,剩下的可以直接推得。

- 时间复杂度: O(TMN)。其中 T 为模数的个数 P
我们当然还不能仅用一个数取膜,需要多个质数(通常用质数)来提高正确率,这并不是一个精确的算法,但在大多数情况下成立。

难道在x比较小的时候,我们计算这个多项式的值也需要暴力搞嘛?

我们智慧的先人秦九韶早就计算了一种计算多项式的简化方法,复杂度O(n)

(你可以在高中数学必修3中学到它)

它大概长这样:

它的代码大概长这样:

1 bool check(int x,int mo)
2 {
3     ll num=0;
4     for(int i=n+1;i>=1;i--)
5         num=(num*x+a[i][mo])%prime[mo];
6     return num==0;
7 } 

于是我们就可以 快速求解多项式的值了。

 

至此,本题就结束了=w=。

复杂度O(TMN),T为选取模数的个数。

Code

 1 #include<cstdio>
 2 #include<algorithm>
 3 #include<cstring> 
 4 
 5 using namespace std;
 6 typedef long long ll;
 7 
 8 int n,m,ans;
 9 char op[20000];
10 int a[500][10];
11 bool vis[2000000];
12 int prime[10]={0,10691,14551,21871,39521,24179};
13 
14 void read(int pos)
15 {
16     bool flag=0;
17     int st=0;
18     scanf("%s",op+1);
19     if(op[1]=='-') flag=1,st=2;
20     else st=1;
21     for(int i=st;i<=strlen(op+1);i++)
22         for(int j=1;j<=5;j++)
23             a[pos][j]=(a[pos][j]*10+op[i]-'0')%prime[j];
24     if(flag)
25         for(int j=1;j<=5;j++)
26             a[pos][j]=(prime[j]-a[pos][j])%prime[j];
27 }
28 
29 bool check(int x,int mo)
30 {
31     ll num=0;
32     for(int i=n+1;i>=1;i--)
33         num=(num*x+a[i][mo])%prime[mo];
34     return num==0;
35 } 
36 
37 int main()
38 {
39     scanf("%d%d",&n,&m);
40     for(int i=1;i<=n+1;i++) read(i);
41     for(int j=1;j<=5;j++)
42     {
43         int limit=min(prime[j]-1,m);
44         for(int i=1;i<=limit;i++)
45             if(!check(i,j))
46                 for(int k=i;k<=m;k+=prime[j])
47                     vis[k]=1;         
48     } 
49     for(int i=1;i<=m;i++)
50         if(!vis[i]) ans++;
51     if(!ans){printf("0");return 0;}
52     else printf("%d\n",ans);
53     for(int i=1;i<=m;i++)
54         if(!vis[i]) printf("%d\n",i);
55     return 0;
56 }
View Code

*  开始交的时候脑子抽了以为1e6是100000于是愉快地RE了三个点233

posted @ 2018-09-28 14:48  cellur925&Chemist  阅读(282)  评论(0编辑  收藏  举报