C++高级算法之运用二次函数公式推导的数论题——小球碰撞(包看包懂)

前言

这题居然是我为数不多的几道在考场上想出正解的数论题(虽然没A),真是太感动了。考完后问了下同学的方法,暴力枚举(从-10000枚举都10000)居然能得到一半的分,也是很神奇了。

这给了我们一个启示,不能保证100%做对的题都要打包搜。

题目

先放出题目惊吓一下你们幼小的心灵。(不是我吹,能一遍理解的都是大佬)

题目描述

 

输入

 

输出

 

样例输入

样例1输入:
2 3 1


样例2输入:
232 320 34272

样例输出

样例1输出:
2.5


样例2输出:
1102656

提示

 

 

好了让我来用人话解释一下:给出两个值a,b,和c,求其中方程(ax+by=c)的解x,y代入E(x)+E(y)的最小值.。min(E(x)+E(y)) 。其中E(x)=ax^{2}/2

那么也就是说是求一个方程的解经过一定计算后的最小值。

在这里科普一下,解二元一次方程我们一般都是用扩展欧几里得算法。我们都知道辗转相除法是求两个数的最大公因数,那么魔改扩展欧几里得算法在这基础上扩展了一下,他不仅可以就出最大公因数,还可以求出如下方程的一个解,也就是特解。

x_{1}+y_{1}=gcd(x,y)2式)。

假装我们都知道ax+by=c1式)这个方程有整数的解的情况下必须满足c|gcd(x,y)(c能被x,y的最大公因数整除)。这里证一下。

将1式的x,y替换一下可得(设gcd(x,y)=d:

ak_{1}d+bk_{2}d=c

合并同类项:

d(ak_{1}+bk_{2})=c

因为a,k1,b,k2都是整数,所以

c|d

那么我们就可以在二式左右两边乘以一个c/d(假定我们已经用扩展欧几里得算法求出了x1,y1)。那么1式就可以转换为:

x1c/d+y1c/d=c.

现在给出扩展欧几里得算法(具体证明请百度)

void exgcd(LL a, LL b,LL &d,LL& x, LL& y)//d为gcd(x,y),x,y为一组特解(公式里的x1,y1)
{
    if(!b)
        x = 1,y = 0,d=a;
    else {
        exgcd(b, a%b, d ,y, x);
        y -= x*(a/b);
    }
}

现在我们已经求出了一组特解。但我们并不知道这组解计算后是否最小,所以我们还要求出通解。

下面给出由特解求出通解的方法:

x=x_{1}+ut

y=y_{1}+vt

(其中t为任意整数,u=c/d,v=-b/d) 

现在知识储备已经完善,现在让我们来思考一下如何求出最小解(当然不是用暴力枚举)

回忆一下当时考试的情景,我正在冥思苦想之时,看到了数学知识补充,嗯?

里面有一句,当x=-b/2a时,y最小,那么这就是此题的突破口了.

因为我们要求的最小值是E(x)+E(y),那么不妨将E(x)+E(y)看成y,再求他的最小值。

所以可以得出式子:E(x)+E(y)=ax^{2}/2+by^{2}/2(3式),又有ax+by=c,化一下可得y=(c-ax)/b,代入到3式之后化简,就可以求出二次函数中的系数a,b,便可以算出最小的x.

这里不给出化简过程,化简后可得rx=c/(a+b)

光算出rx还不够,我们还要算出ry,这里直接通过x=x_{1}+ut这个式子求出t=(rx-x)/u,便可以算出ry来.

对了,还有,因为rx不一定是整数,而t是整数。我的解决方案是求t取t,t-1,t+1算出来之后的最小值。

代码

 

#include 
#include 
  
using namespace std;
  
#define LL long long
#define N 100010
#define mem(a,n) memset(a,n,sizeof(a))
  
int read() {
    int f=1,s=0;char a=getchar();
    while(a<'0' || a>'9') { if(a=='-') f=-1; a=getchar(); }
    while(a>='0' && a<='9') { s=s*10+a-'0'; a=getchar(); }
    return f*s;
}
 
LL m1=read(),m2=read(),P=read(),va,vb,d,u,v,T;
double rva;
  
void exgcd(LL a, LL b,LL &d,LL& x, LL& y)
{
    if(!b)
        x = 1,y = 0,d=a;
    else {
        exgcd(b, a%b, d ,y, x);
        y -= x*(a/b);
    }
}
 
double E(double v,double m) {
    return m*v*v/2;
}
 
int main() {
    exgcd(m1,m2,d,va,vb);
    if(P%d!=0) {
        cout<<"-1";
        return 0;
    }
    va*=P/d; 
    vb*=P/d;
    u=m2/d,v=-m1/d;
    rva=P*1.0/((m1+m2)*1.0);
    T=(rva-va*1.0)/u;
    double ans=min( E(va+u*T,m1)+E(vb+v*T,m2),min(E(va+u*(T+1),m1)+E(vb+v*(T+1),m2),E(va+u*(T-1),m1)+E(vb+v*(T-1),m2)));
    LL ans1=ans;
    if(ans1==ans)
        cout<

 

你可能感兴趣的:(C++,数论)