NOIp 2014 解方程 【数学/秦九韶算法/大数取膜】By cellur925
题意:求高次方程的解及其个数。其中
1°
我们知道,高次方程是没有求根公式的。但是利用逆向思维,我们可以进行“试根法”,因为题目中给出了所求根的范围。但是多项式系数过于吓人,达到了sxbk的1e10000.longlong显然盛不下。只能看做字符串处理。然而即使是处理成字符串,我们也不可能真的去乘这么多。
2°
考虑取膜。我们把多项式系数进行取膜,它的相对效果和不取膜是一样的。(想一想,为什么)
除了对系数取膜,我们还可以考虑对x取膜。
- 如果 X 真的是一个根,那么取模后肯定是 0;但反之则是不确定。
- 逆否命题:如果取模之后不是 0,那么 X 肯定不是一个根。
- 我们就利用上面的这条性质来判断即可。- 考虑一个较小的模数 p。
- 如果对于 0<x<p 来说,代入 x 计算后不是 0,则那么对于 x+p,
x+2p, x+3p...,这些数代入计算后都不可能为 0。
- 所以我们只需要验证[1,p)之间的数,剩下的可以直接推得。
- 时间复杂度: O(TMN)。其中 T 为模数的个数 P。
我们当然还不能仅用一个数取膜,需要多个质数(通常用质数)来提高正确率,这并不是一个精确的算法,但在大多数情况下成立。
3°
难道在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 }
* 开始交的时候脑子抽了以为1e6是100000于是愉快地RE了三个点233
独立意志与自由思想是必须争的,且须以生死力争。