洛谷 P2312 解方程

题目

首先,可以确定的是这题的做法就是暴力枚举x,然后去计算方程左边与右边是否相等。

但是noip的D2T3怎么会真的这么简单呢?卡常卡的真是熟练 你需要一些优化方法。

首先可以用秦九韶公式优化一下方程左边的计算方法:

左边=(((..(a[n]*x)+a[n-1])*x+..+a[1])*x+a[0]

然后我就试着直接去算:

#include<cstdio>
typedef long long LL;
LL a[110],ans[1001000];
LL n,m,aa;
int main()
{
    LL i,j;
    scanf("%lld%lld",&n,&m);
    for(i=0;i<=n;i++)
        scanf("%lld",&a[i]);
    for(i=n;i>=1;i--)
        for(j=1;j<=m;j++)
        {
            ans[j]+=a[i];
            ans[j]*=j;
        }
    for(i=1;i<=m;i++)
        ans[i]+=a[0];
    for(i=1;i<=m;i++)
        if(ans[i]==0)
            aa++;
    printf("%lld\n",aa);
    for(i=1;i<=m;i++)
        if(ans[i]==0)
            printf("%lld\n",i);
    return 0;
}

然后就Wa(30)掉了...没看清数据范围(ai最长有10000位,而不是最大10000)

也许你会想到高精度运算,但是很容易发现,这题的数据范围太大,直接高精度暴力算太慢。

此时有一个小trick:对于每个a[n],读入的时候对某一些大质数取模。对于每个枚举出的x,就用取模过的a[i]去算。如果用对好几组对不同质数取模得到的a[i]算都能得到0,那么就认为x是合法的。

直觉上可能觉得很容易被卡掉?但事实上一点也不容易被卡....好像还是正解..

那么对于负的a[i]怎么去取模呢?很简单,读入的时候看一下符号位,然后按照正数的方式取模(每读入一位将当前余数乘10再加当前位再取模)。处理完整个数后,如果记录的符号位是负数,那么就将余数变为模数-余数。

然后我去交...然后就T(70)掉了...还是太慢。(而且O2都救不了我...)

//#pragma GCC optimize(2)
#include<cstdio>
#include<cstring>
typedef long long LL;
LL a[5][110],ans[5][1001000];
LL prime[]={179424629,179424667,179424671,179424673,179424691};
LL pnum=5,n,m,aa;
char c;
bool nok[1010000];
int main()
{
    //freopen("testdata.in","r",stdin);
    //freopen("testdata.ss","w",stdout);
    LL i,j,i1,fl,sl,p;
    scanf("%lld%lld",&n,&m);
    c=getchar();
    for(i=0;i<=n;i++)
    {
        while(!((c>='0'&&c<='9')||c=='-'))	c=getchar();
        if(c=='-')
            fl=1,c=getchar();
        else
            fl=0;
        for(j=0;j<pnum;j++)
        {
            for(i1=fl;c>='0'&&c<='9';i1++)
            {
                a[j][i]=(a[j][i]*10+c-'0');
                while(a[j][i]>=prime[j])	a[j][i]-=prime[j];
                //ans[j][i]-=prime[j]
                c=getchar();
            }	
            if(fl)	a[j][i]=prime[j]-a[j][i];
        }
    }
    for(p=0;p<pnum;p++)
    {
        for(j=1;j<=m;j++)
            if(!nok[j])
                for(i=n;i>=1;i--)
                {
                    ans[p][j]=(ans[p][j]+a[p][i])*j%prime[p];
                }	
        for(j=1;j<=m;j++)
            if(!nok[j])
            {
                ans[p][j]+=a[p][0];
                while(ans[p][j]>=prime[p])	ans[p][j]-=prime[p];
            }	
        for(j=1;j<=m;j++)
            if(ans[p][j]!=0)
                nok[j]=1;
    }
    for(i=1;i<=m;i++)
        if(!nok[i])
            aa++;
    printf("%lld\n",aa);
    for(i=1;i<=m;i++)
        if(!nok[i])
            printf("%lld\n",i);
    return 0;
}

卡了很久的常之后,我放弃了,去看了题解。

原来这个算法是可以接着优化的:http://www.cnblogs.com/NaVi-Awson/p/7566889.html

根据的就是:f(x)≡0(modp),则f(x+p)≡0(modp)

这样子可以快速过滤掉一些m。(怎么觉得是卡常呢...)

然后,我又去交..又T(70)了..

原因:质数选的太大,这样是不能筛掉什么m的

#pragma GCC optimize(2)
#include<cstdio>
#include<cstring>
typedef long long LL;
LL a[5][110],ans[5][1001000];
LL prime[]={179424629,179424667,179424671,179424673,179424691};
LL pnum=5,n,m,aa;
char c;
bool nok[1010000];
int main()
{
    //freopen("testdata.in","r",stdin);
    //freopen("testdata.ss","w",stdout);
    LL i,j,i1,fl,sl,p,k;
    scanf("%lld%lld",&n,&m);
    c=getchar();
    for(i=0;i<=n;i++)
    {
        while(!((c>='0'&&c<='9')||c=='-'))	c=getchar();
        if(c=='-')
            fl=1,c=getchar();
        else
            fl=0;
        for(j=0;j<pnum;j++)
        {
            for(i1=fl;c>='0'&&c<='9';i1++)
            {
                a[j][i]=(a[j][i]*10+c-'0');
                while(a[j][i]>=prime[j])	a[j][i]-=prime[j];
                //ans[j][i]-=prime[j]
                c=getchar();
            }	
            if(fl)	a[j][i]=prime[j]-a[j][i];
        }
    }
    for(p=0;p<pnum;p++)
    {
        for(j=1;j<=m;j++)
            if(!nok[j])
            {
                for(i=n;i>=1;i--)
                    ans[p][j]=(ans[p][j]+a[p][i])*j%prime[p];
                ans[p][j]+=a[p][0];
                while(ans[p][j]>=prime[p])	ans[p][j]-=prime[p];
                if(ans[p][j]!=0)
                    for(k=j;k<=m;k+=prime[p])
                        nok[k]=1;
            }
    }
    for(i=1;i<=m;i++)
        if(!nok[i])
            aa++;
    printf("%lld\n",aa);
    for(i=1;i<=m;i++)
        if(!nok[i])
            printf("%lld\n",i);
    return 0;
}
改进:可以用比较小的质数筛掉大部分m,然后用大质数筛掉剩下(也许存在的)不合法的m。

AC代码:

//#pragma GCC optimize(2)
#include<cstdio>
#include<cstring>
typedef long long LL;
LL a[5][110],ans[5][1001000];
LL prime[]={81799,81817,179424671,179424673,179424691};
LL pnum=5,n,m,aa;
char s[10100];
bool nok[1010000];
int main()
{
    //freopen("testdata.in","r",stdin);
    //freopen("testdata.ss","w",stdout);
    LL i,j,i1,fl,sl,p,k;
    scanf("%lld%lld",&n,&m);
//	c=getchar();
//	for(i=0;i<=n;i++)
//	{
//		while(!((c>='0'&&c<='9')||c=='-'))	c=getchar();
//		if(c=='-')
//			fl=1,c=getchar();
//		else
//			fl=0;
//		for(j=0;j<pnum;j++)
//		{
//			for(i1=fl;c>='0'&&c<='9';i1++)
//			{
//				a[j][i]=(a[j][i]*10+c-'0');
//				while(a[j][i]>=prime[j])	a[j][i]-=prime[j];
//				//ans[j][i]-=prime[j]
//				c=getchar();
//			}	
//			if(fl)	a[j][i]=prime[j]-a[j][i];
//		}
//	}
    for(i=0;i<=n;i++)
    {
        scanf("%s",s);
        if(s[0]=='-')
            fl=1;
        else
            fl=0;
        sl=strlen(s);
        for(j=0;j<pnum;j++)
        {
            for(i1=fl;i1<sl;i1++)
                a[j][i]=(a[j][i]*10+s[i1]-'0')%prime[j];
            if(fl)	a[j][i]=prime[j]-a[j][i];
        }
    }
    for(p=0;p<pnum;p++)
    {
        for(j=1;j<=m;j++)
            if(!nok[j])
            {
                for(i=n;i>=1;i--)
                    ans[p][j]=(ans[p][j]+a[p][i])*j%prime[p];
                ans[p][j]+=a[p][0];
                while(ans[p][j]>=prime[p])	ans[p][j]-=prime[p];
                if(ans[p][j]!=0)
                    for(k=j;k<=m;k+=prime[p])
                        nok[k]=1;
            }
    }
    for(i=1;i<=m;i++)
        if(!nok[i])
            aa++;
    printf("%lld\n",aa);
    for(i=1;i<=m;i++)
        if(!nok[i])
            printf("%lld\n",i);
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值