poj 2142 数论 扩展欧几里得

************* 传送门 不谢*****************
name: c0de
第一次正式地写博客,欢迎吐槽。
*****解析题意*****
题目要称重量为d的物品,有a,b两种砝码,a b d从键盘输入,输出一组x,y(分别是两个砝码的个数)使得能够称量此物品,首先要明确,砝码可以与物品同侧也可以异侧。
 先说说我的思路吧 :
                           开始我对下滑线一句这么理解:可以分情况呗!
                          so:1. ax + by = d                      2. ax = by + d 即 ax - by = d             3.ax + d = by 即  -ax + by = d
                          然后我开始思这么讨论是不是太烦了,尼玛的三个情况。    
                          后来发现这三个方程就是一个方程        ax + by = d(仔细理解理解)
                          为帮助你理解我将三个方程写成如下形式:
                          1. ax + by = d                             2.ax + b(-y) = d                                 3.a(-x) + by =d     是不是恍然大悟?!
                          只是x y 的正负问题啊,这里的正负只是表示和d是同侧还是异侧,我们最后考虑的是数量,因此后面取abs即可。(自此解决了20%的问题吧可以说)
                         
                          扩展欧几里得详解
                          问题转化为求解此方程整数解的问题     (其实写过poj  1061 青蛙的约会这道题的人  一下子反应过来 这个方程就是  “扩展欧几里得”   算法,如果没有听过这个算法也不要紧,这个算法十分简单请你耐心看完数论中有关此方程求解的方法即其代码转化)ps  如果推理过程不想看  那就老老实实记住结论  反正推理过程也没啥用  哈哈
                           看看这里的讲解 挺好的
                           看完之后 你就会求解  此线性同余方程了    厉害了 老铁!!
                           现在问题解决一半了!!
                           兄弟们  我先上传我的code然后说说里面的精髓  (笑脸
                           #include<iostream>
#include<cstdlib>
using namespace std;
typedef int ll;
ll ex_gcd(ll a,ll b, ll &x,ll &y)   
              {
        if(b == 0)
        {
                x = 1;
                y = 0;
                return a;
        }
        else
        {
                ll r = ex_gcd(b,a%b,y,x);
                y  -= (a/b)*x;
                return r;
        }
}                                                                                                             /*  int   abs(int   j);  
                                                                                                                                long   int   labs(long   int   j);  
                                                                                                                                  long   long   int   llabs(long   long   int   j);  
                                                                                                                                 */
/*我先说这儿 ,经过oj上ce几次,取绝对值函数竟然还有这么多的学问,首先c++里<cmath> 中abs是对double取绝对值的,不知道是poj编译器的问题还是怎么回事,而在<cstdlid>中确是对int取,要想对long 或者longlong取 用labs  和llasb 即可  poj否则会ce,但是我在cb上跑的没毛病。(一个小申明 兄弟们以后也要注意)
/*这个函数就是ex_gcd  大名鼎鼎的扩展欧几里得,大伙多练几次  背下来。
        从此分析题目要求的解:
                1.  求|x|+|y|的最小值   
                      看过扩展欧几里得的兄弟都知道    x y的通解就是 |x0+b/g * t| +|y0 - a/g *t|             (这个东西非常重要  因为一般题目难度不在写扩展欧几里得,主要设置难度在求一组或几组有约束条件的解   因为一旦方程有解  就有无穷多组解   而这个含参数t的方程  就是 题目的入手点,其中g是ab的最大公因数,x0,y0是通过函数求解的一组解,虽然我们不知道这组解是多少 ,因为这组解释递归调用的结果。)
                      这个方程的极小值怎么求呢?
                      1. 先想想绝对值函数的图像,是不是一个   V   呢? 
                        那么两个绝对值函数  组合在一起 是不是 也 “  大致  ”  像个V  呢 ?   好好想想,最好在纸上画一画  ,  那么它的最小值在哪儿 取呢?
                        哥们儿直接告诉你   最小点 由 斜率 大的决定 ,什么?不信  ?  你可以自己画画试试看喽。
                        然后这儿有一个小优化  :   我们普通的想法肯定是分类斜率的大小(这儿的斜率 就分别是 b/g  和a/g  那么只要 比较ab的大小,我看了大佬写的报告 ,牛的一批,人家就假设a<b,开始判断一下,如果不成立,把ab的值互换,最后输出的时候反着输出就行了,其实就是xy的值也互换了而已 ,仔细想想 ,这个优化能减少 将近一小半的代码)。
                      2.最小点的问题解决了,即最小点的t在x的零点取到(如果你假设的是a>b那么就在y的零点取t,这个必须相同,这是这个优化思想的精髓)
                      3.接下来的问题好办了,我们只要枚举  这个“零点”  旁边的点就可以了,有兄弟就会问我,不都把零点求出来了吗 ,还枚举个毛 ?    我想说 兄弟,你以为函数真正的最小值点是个整数  ??  我们求的这个点只能说是一个最接近零点的整数点。    
                        4. 枚举零点旁边的点,暂且把求出的点叫做是假零点,那么这个假零点到底是在零点左边还是在零点右边额?、 
                         说到这儿  就是为了引出第二个优化  
                           想一想   我们一般 碰到这种情况   肯定是if   else   if  .....   巨他妈滴烦  对不对  !!
                          我们可不可以统一一下呢    就是说把这个假零点 同意放在   零点的左边 或者是右边,然后枚举的下一个点   只不过  是t+1或者是t-1而已(这根据你的左边还是右边的 假设而定)
                          这个优化的精髓在于我code里的while 这个while   就把   这个假零点   归于 零点的左侧,然后枚举的下一个零点  只需要   是t+1即可,
                           题目做到这里,基本也做完了  ,  但我在这儿继续 说一下后来的一个优化
                       5. 继续优化       
                        我们把两组可疑解求出来了,但是题目要求是两个条件,先满足条件一,后满足条件2,一般情况我们可能会写嵌套条件吧,又是 一大推,很烦,我们的code要 优美   更要清晰  易读
                          精髓 在于        if((x1 + y1 < x2 + y2)||((x1 + y1 == x2 + y2)&&(a*x1 + b*y1)<(a*x2 + b*y2)))
                         这个条件语句    咋一看这是关于  第一组解成立的两种情况,但我们想想,是不是   至少有一组解 符合题意呢   ?   binggo     !
                          下组 解的条件  就是一个   else    哈哈!!
               ok               完了        !          欢迎观看  报告会。*/
                       以下是code
int main()
{
        ll a,b,d;
        ll x,y;
        bool flag;
        while(cin >> a >> b >> d)
        {
                flag = false;
                if(a+b+d==0) break;
                if(a > b)
                {
                        swap(a,b);
                        flag = true;
                }
                ll g = ex_gcd(a,b,x,y);
                a /= g,b /= g,d /= g;
                x *= d,y *= d;
                ll t = -1*(x/b);
                while(x + b*t > 0)
                {
                        t--;
                }
                ll rx,ry;
                ll x1 = abs(x + b*t);
                ll y1 = abs(y - a*t);
                t++;//小优化
                ll x2 = abs(x + b*t);
                ll y2 = abs(y - a*t);//短短几行 把零点 旁边的两个点 补充不漏地  枚举出来 666
                if((x1 + y1 < x2 + y2)||((x1 + y1 == x2 + y2)&&(a*x1 + b*y1)<(a*x2 + b*y2)))
                {
                         rx = x1;
                         ry = y1;
                }
                else
                {
                         rx = x2;
                         ry = y2;//这几行更是精髓 ,一个条件语句把两个条件涵盖
                }
                if(flag)
                {
                        cout << ry << " " << rx << endl;
                }
                else
                {
                        cout << rx << " " << ry << endl;
                }
        }
        return 0;
}
/*      稍微总结一下吧,这道题就是扩展欧几里得筛选目标解的一个问题,但我们有思路才是小小的一步,关键是如何用code实现,一旦少了其中的优化,我们的code就会十分的繁琐,更可能会导致我们心里乱套而把逻辑写错,可能我们思想里的一个小优化  展现在code中就是少了很多代码,容错率的提高。*/
                              每日诗句共勉              ----------不以规矩,无以成方园。--孟子
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值