zoukankan      html  css  js  c++  java
  • 扩展欧几里德 SGU 106

    题目链接:http://acm.sgu.ru/problem.php?contest=0&problem=106

     

    题意:求ax + by + c = 0在[x1, x2], [y1, y2]区间内有多少组解?

    解析:

    令c = -c有ax + by = c,可用扩展欧几里德解方程解出特解

    当然要先考虑a = 0, b = 0, c = 0的情况进行特判

    例如:a = 0, b = 1, c = 3,x∈[x1, x2], y∈[3, 4]

    即可得知有方程有x2-x1+1个解,因为x可以区间内任意,且y=3这个解在区间内,其他情况同理了

    ②然后就是关键了,用的是扩展欧几里德通解式:(设一整数k,x0为x的特解)

    1、x1 <= x0+k*b <= x2

    2、y0 = (c - a*x0) / b

    3、y1 <= y0+k'*b <= y2

    解出k和k'的范围[s, e]!

    注意了,例如解x1 <= x0+k*b:

    当b<0时两边同时除以b,<=要变成>=号

    --->k <= (x1-x0) / b

    然后最关键就是除不尽应该向什么方向舍入?

    区间[s, e]的舍入方向:

    正数s, e的情况:

    可见s = (x1-x0)/b还要+1,e直接除即可
    负数s, e的情况:

    可见e = (x1-x0)/b还要-1,s直接除即可

    ③最后得到x的k的范围[s1, e1], y的k'的范围[s2, e2]

    因为x增加必然导致y减小,所以有:

    举个例子,如图所示:令-e2作为s2,令-s2作为e2,答案为[-e2, e1]
     

    所以答案ans = min (e1, e2') - max (s1, s2') + 1;

    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <cstring>
    #include <stdlib.h>
    #include <map>
    #include <algorithm>
    #include <math.h>
    using namespace std;
    #define M 1005
    #define LL long long
    LL max(LL a,LL b){
    	return a>b?a:b;
    }
    LL min(LL a,LL b){
    	return a<b?a:b;
    }
    LL gcd (LL a, LL b)
    {
    	return b ? gcd (b, a%b) : a;
    }
    
    void Egcd (LL a, LL b, LL &x, LL &y)
    {
    	if (b == 0)
    	{
    		x = 1, y = 0;
    		return ;
    	}
    	LL tp;
    	Egcd (b, a%b, x, y);
    	tp = x;
    	x = y;
    	y = tp - a/b*y;
    }
    
    LL cal (LL f, LL n, int key)			//处理舍入问题
    {
    	if (f % n == 0) return f/n;
    	if (key == 0)
    	{
    		if (f * n < 0) return f/n;
    		return f/n+1;
    	}
    	if (f * n < 0) return f/n-1;
    	return f/n;
    }
    
    LL a, b, c;
    LL solve (LL &x1, LL &x2, LL &y1, LL &y2)
    {
    	LL d, x, y, s1, e1, s2, e2;
    	c = -c;
    	/***********************特判***********************/
    	if (a == 0 && b == 0 && c == 0)
    		return (x2-x1+1) * (y2-y1+1);
    	if (a == 0 && b == 0) return 0;
    	if (a == 0)
    	{
    		if (c % b != 0) return 0;
    		y = c / b;
    		if (y >= y1 && y <= y2) return x2 - x1 + 1;
    		return 0;
    	}
    	if (b == 0)
    	{
    		if (c % a != 0) return 0;
    		x = c / a;
    		if (x >= x1 && x <= x2) return y2 - y1 + 1;
    		return 0;
    	}
    	/***********************特判***********************/
    	d = gcd (a, b);
    	if (c % d != 0) return 0;
    	a /= d, b /= d, c /= d;
    	Egcd (a, b, x, y);
    
    	x *= c;
    	x = (x % b + b) % b;						 //x的特解
    	s1 = cal (x1-x, b, b<0);						//s1
    	e1 = cal (x2-x, b, b>0);						//e1
    
    	y = (c - a*x) / b;							 //有了x,求对应的y
    	e2 = -cal (y1-y, a, a<0);					//e2' = -s2
    	s2 = -cal (y2-y, a, a>0);					//s2' = -e2
    
    	if (b < 0) s1 ^= e1, e1 ^= s1, s1 ^= e1;		//b为负数,变号导致区间头尾互换
    	if (a < 0) s2 ^= e2, e2 ^= s2, s2 ^= e2;		//同理
    
    	LL ans = min (e1, e2) - max (s1, s2)  + 1;
    	if (ans < 0) ans = 0;
    
    	return ans;
    }
    
    int main()
    {
    	LL x1, x2, y1, y2;
    	while (~scanf ("%lld%lld%lld", &a, &b, &c))
    	{
    		scanf ("%lld%lld%lld%lld", &x1, &x2, &y1, &y2);
    		printf ("%lld
    ", solve (x1, x2, y1, y2));
    	}
    	return 0;
    }
    

    这里讲下:

    扩展欧几里德算法是用来在已知a, b求解一组p,q使得p * a+q * b = Gcd(p, q) (解一定存在,根据数论中的相关定理)。扩展欧几里德常用在求解模线性方程及方程组中。下面是一个使用C++的实现:
      int exGcd(int a, int b, int &x, int &y)
      {
      if(b == 0)
      {
      x = 1;
      y = 0;
      return a;
      }
      int r = exGcd(b, a % b, x, y);
      int t = x;
      x = y;
      y = t - a / b * y;
      return r;
      }
      把这个实现和Gcd的递归实现相比,发现多了下面的x,y赋值过程,这就是扩展欧几里德算法的精髓。
      可以这样思考:
      对于a' = b, b' = a % b 而言,我们求得 x, y使得 a'x + b'y = Gcd(a', b')
      由于b' = a % b = a - a / b * b (注:这里的/是程序设计语言中的除法)
      那么可以得到:
      a'x + b'y = Gcd(a', b') ===>
      bx + (a - a / b * b)y = Gcd(a', b') = Gcd(a, b) ===>
      ay +b(x - a / b*y) = Gcd(a, b)
      因此对于a和b而言,他们的相对应的p,q分别是 y和(x-a/b*y)
      补充:关于使用扩展欧几里德算法解决不定方程的办法
      对于不定整数方程pa+qb=c,若 c mod Gcd(p, q)=0,则该方程存在整数解,否则不存在整数解。
      上面已经列出找一个整数解的方法,在找到p * a+q * b = Gcd(p, q)的一组解p0,q0后,p * a+q * b = Gcd(p, q)的其他整数解满足:
      p = p0 + b/Gcd(p, q) * t 
      q = q0 - a/Gcd(p, q) * t(其中t为任意整数)
      至于pa+qb=c的整数解,只需将p * a+q * b = Gcd(p, q)的每个解乘上 c/Gcd(p, q) 即可。

    转:
    首先扩展欧几里德主要是用来与求解线性方程相关的问题,所以我们从一个线性方程开始分析。现在假设这个线性方程为a*x+b*y=m,如果这个线性方程有解,那么一定有gcd(a,b) | m,即a,b的最大公约数能够整除m(m%gcd(a,b)==0)。证明很简单,由于a%gcd(a,b)==b%gcd(a,b)==0,所以a*x+b*y肯定能够整除gcd(a,b),如果线性方程成立,那么就可以用m代替a*x+b*y,从而得到上面的结论,利用上面的结论就可以用来判断一个线性方程是否有解。
     
          那么在a*x+b*y=m这个线性方程成立的情况下,如何来求解x和y呢?
     
          1.令a1=a/gcd(a,b),b1=b/gcd(a,b),m1=m/gcd(a,b)。如果我们能够首先求出满足a*x1+b*y1=gcd(a,b)这个方程的x1和y1,那么x=x1*m1,y=y1*m1就可以求出来了。由欧几里德算法gcd(a,b)=gcd(b,a%b),所以a*x1+b*y1=gcd(a,b)=gcd(b,a%b)=b*x2+(a%b)*y2,现在只要做一些变形就可以得到扩展欧几里德算法中的用到的式子了。令k=a/b(商),r=a%b(余数),那么a=k*b+r。所以r=a-k*b,带入上式,得到a*x1+b*y1=b*x2+(a-(a/b)*b)y2=a*y2+b*(x2-(a/b)*y2) => x1=y2,y1=x2-(a/b)*y2。有了这两个式子我们就知道了在用欧几里德求最大公约数的时候,相应的参数x,y的变化。现在再回过头来看一下扩展欧几里德算法的代码就很好理解了,实际上扩展欧几里德就是在求a和b的最大公约数的同时,也将满足方程a*x1+b*y1=gcd(a,b)的一组x1和y1的值求了出来。下面代码中突出的部分就是标准的欧几里德算法的代码。
     
    __int64 exGcd(__int64 a,__int64 b,__int64 &x,__int64 &y){
        if(b==0){
            x=1;
            y=0;
            return a;
        }
        __int64 g=exGcd(b,a%b,x,y);
        __int64 temp=x;
        x=y;
        y=temp-(a/b)*y;
        return g;
    }
     
         2.那么x,y的一组解就是x1*m1,y1*m1,但是由于满足方程的解无穷多个,在实际的解题中一般都会去求解x或是y的最小正数的值。以求x为例,又该如何求解呢?还是从方程入手,现在的x,y已经满足a*x+b*y=m,那么a*(x+n*b)+b*(y-n*a)=m显然也是成立的。可以得出x+n*b(n=…,-2,-1,0,1,2,…)就是方程的所有x解的集合,由于每一个x都肯定有一个y和其对应,所以在求解x的时候可以不考虑y的取值。取k使得x+k*b>0,x的最小正数值就应该是(x+k*b)%b,但是这个值真的是最小的吗??如果我们将方程最有两边同时除以gcd(a,b),则方程变为a1*x+b1*y=m1,同上面的分析可知,此时的最小值应该为(x+k*b1)%b1,由于b1<=b,所以这个值一定会小于等于之前的值。在实际的求解过程中一般都是用while(x<0)x+=b1来使得为正的条件满足,为了更快的退出循环,可以将b1改为b(b是b1的倍数),并将b乘以一个倍数后再加到x上。

    参考:

    http://www.cnblogs.com/void/archive/2011/04/18/2020357.html

    http://972169909-qq-com.iteye.com/blog/1538245

    版权声明:本文为博主原创文章,未经博主允许不得转载。

    today lazy . tomorrow die .
  • 相关阅读:
    Linux 关机和重启命令
    Linux ubuntu安装ftp服务器
    C++ map和unsorted_map底层实现
    C++中的那些容器在使用时,哪些情况下迭代器会失效
    虚函数表的构造
    C++容器 priority_queue,堆的实现
    c++11中的move是否会改变对象的地址
    (转)关于linux中内核编程中结构体的赋值操作(结构体指定初始化)
    无参方法
    类和对象
  • 原文地址:https://www.cnblogs.com/france/p/4808715.html
Copyright © 2011-2022 走看看