3751: [NOIP2014]解方程
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 2992 Solved: 618
[ Submit][ Status][ Discuss]
Description
已知多项式方程:
a0+a1*x+a2*x^2+...+an*x^n=0
求这个方程在[1,m]内的整数解(n和m均为正整数)。
Input
第一行包含2个整数n、m,每两个整数之间用一个空格隔开。
接下来的n+1行每行包含一个整数,依次为a0,a1,a2,...,an。
Output
第一行输出方程在[1,m]内的整数解的个数。
接下来每行一个整数,按照从小到大的顺序依次输出方程在[1,m]内的一个整数解。
Sample Input
2 10
2
-3
1
2
-3
1
Sample Output
2
1
2
1
2
HINT
对于100%的数据,0<n≤100,|ai|≤1010000,an≠0,m≤1000000。
Source
题解:数论
这个题70%应该比较好拿,直接用几个质数当模数对整个方程取模,判断是否等于0,直接输出答案即可。
然后需要利用一个知识:f[x]=0(mod p) 则f[x+p]=0(mod p)
利用这个我们可以选5,6个质数,预处理出1-p-1范围内的左边式子的值,然后检查是否是答案的时候,对于较大的数直接检查取模后的结果。这样做时间复杂度应该是是O(5*p*n+5*m)
但是其实有更快的方法。就是我们先选取一个小质数,然后用它求出1-p-1内的合法解(可能),然后再用一个大质数去check,如果是合法的就把x+p扔进待检测的队列,直到所有可能的解都检测完为止。这样的时间复杂度应该是O(np+n*n*m/p)发现化简后是对勾,那么当小p=sqrt(n*m)的时候,时间辅助度最小大概是O(2*n*sqrt(nm))
为什么是n*n*m/p呢?因为一个n次方程最多有n个可行解(根据拉格朗日定理),那么刚开始最多选出n个解,然后每次在合法解的基础上+p,然后最多+m/p次,然后再算上每次检测n的复杂度。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 100003
#define LL long long
using namespace std;
int a[10][N],pre[10][N],mark[10][N];
int mod[10]={11261,19997,22877,21893,14843},ans[N*10];
int n,m;
char s[100003];
int calc(int x)
{
int ans=0;
for (int i=0;i<=n;i++)
ans=(ans+pre[x][i]*a[x][i])%mod[x];
if (ans==0) return 1;
return 0;
}
bool check(int x)
{
for (int i=0;i<5;i++)
if (!mark[i][x%mod[i]]) return false;
return true;
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d%d",&n,&m);
for (int i=0;i<=n;i++) {
scanf("%s",s+1);
int len=strlen(s+1); int flag=0;
for (int j=0;j<5;j++) {
if (s[1]!='-') a[j][i]=s[1]-'0';
else a[j][i]=0,flag=1;
}
for (int j=0;j<5;j++) {
for (int k=2;k<=len;k++)
a[j][i]=(a[j][i]*10+s[k]-'0')%mod[j];
if (flag) a[j][i]=-a[j][i];
}
}
for (int j=0;j<5;j++) {
for (int i=1;i<mod[j];i++) {
pre[j][0]=1;
for (int k=1;k<=n;k++) pre[j][k]=(pre[j][k-1]*i)%mod[j];
mark[j][i]=calc(j);
}
}
int tot=0;
for (int i=1;i<=m;i++)
if (check(i)) ans[++ans[0]]=i;
printf("%d\n",ans[0]);
for (int i=1;i<=ans[0];i++) printf("%d\n",ans[i]);
}