知识点 - 线性丢番图方程 Linear Diophantine Equations

知识点 - 线性丢番图方程 Linear Diophantine Equations

解决问题类型:

对于不定方程(a,b,c是给定参数,x,y为系数)
a x + b y = c ax + by = c ax+by=c

  1. 找到一个解
  2. 找到所有解
  3. 找到给定区间内的解的个数和解集
  4. 找到x+y最小的解

推导、代码及复杂度

0. a = b = 0特判;a=0 or b=0 也特判
  1. 找到一个解

    O ( l o g n ) O(logn) O(logn)

    对ab使用扩展gcd得到 g , x g , y g g ,x_g, y_g g,xg,yg
    a x g + b y g = g a x_g + b y_g =g axg+byg=g
    如果 g ∣ c g|c gc,则有解。两边乘 c g \frac{c}{g} gc
    a ⋅ x g ⋅ c g + b ⋅ y g ⋅ c g = c a \cdot x_g \cdot \frac{c}{g} + b \cdot y_g \cdot \frac{c}{g} = c\\ axggc+byggc=c
    得到一组特解
    x 0 = x g ⋅ c g y 0 = y g ⋅ c g . x_0 = x_g \cdot \frac{c}{g}\\ y_0 = y_g \cdot \frac{c}{g}. x0=xggcy0=yggc.

    int gcd(int a, int b, int &x, int &y) {
        if (a == 0) {
            x = 0; y = 1;
            return b;
        }
        int x1, y1;
        int d = gcd(b%a, a, x1, y1);
        x = y1 - (b / a) * x1;
        y = x1;
        return d;
    }
    
    bool find_any_solution(int a, int b, int c, int &x0, int &y0, int &g) {
        g = gcd(abs(a), abs(b), x0, y0);
        if (c % g) {
            return false;
        }
    
        x0 *= c / g;
        y0 *= c / g;
        if (a < 0) x0 = -x0;
        if (b < 0) y0 = -y0;
        return true;
    }
    
  2. 找到所有解

    我们将一组特解带回方程
    a ⋅ x 0 + b ⋅ y 0 = c a \cdot x_0 + b \cdot y_0 = c ax0+by0=c
    x 0 + = b / g x_0+=b / g x0+=b/g 的同时 y 0 − = a / g y_0-=a / g y0=a/g ,方程右边不变
    a ⋅ ( x 0 + b g ) + b ⋅ ( y 0 − a g ) = a ⋅ x 0 + b ⋅ y 0 + a ⋅ b g − b ⋅ a g = c a \cdot \left(x_0 + \frac{b}{g}\right) + b \cdot \left(y_0 - \frac{a}{g}\right) = a \cdot x_0 + b \cdot y_0 + a \cdot \frac{b}{g} - b \cdot \frac{a}{g} = c a(x0+gb)+b(y0ga)=ax0+by0+agbbga=c
    于是得到所有情况的解集
    x = x 0 + k ⋅ b g y = y 0 − k ⋅ a g x = x_0 + k \cdot \frac{b}{g}\\ y = y_0 - k \cdot \frac{a}{g} x=x0+kgby=y0kga

  3. 找到解的个数和在给定区间的解

    现在 x y xy xy有限制: [ m i n x ; m a x x ] [min_x; max_x] [minx;maxx] [ m i n y ; m a x y ] [min_y; max_y] [miny;maxy]

    首先 O ( 1 ) O(1) O(1)地找到最小的 x x x使得 x ≥ m i n x x \ge min_x xminx ,以及最大的 x x x使得 x ≤ m a x x x \le max_x xmaxx,记为 l x 1 , r x 1 l_{x1},r_{x1} lx1rx1

    然后类似地找到最小的 y y y使得 y ≥ m i n y y \ge min_y yminy ,以及最大的 y y y使得 y ≤ m a x y y \le max_y ymaxy,并找到对应的x值记为 l x 2 , r x 2 。 l_{x2},r_{x2}。 lx2rx2

    最后对区间 [ l x 1 , r x 1 ] [l_{x1}, r_{x1}] [lx1,rx1] [ l x 2 , r x 2 ] [l_{x2}, r_{x2}] [lx2,rx2]求交集得到 [ l x , r x ] [l_x, r_x] [lx,rx]

    模板中用 a g x + b g y = c g \frac{a}{g} x + \frac{b}{g} y = \frac{c}{g} gax+gby=gc来使 gcd ⁡ ( a g , b g ) = 1 \gcd(\frac{a}{g}, \frac{b}{g}) = 1 gcd(ga,gb)=1以简化计算。

    void shift_solution(int & x, int & y, int a, int b, int cnt) {
        x += cnt * b;
        y -= cnt * a;
    }
    
    int find_all_solutions(int a, int b, int c, int minx, int maxx, int miny, int maxy) {
        int x, y, g;
        if (!find_any_solution(a, b, c, x, y, g))
            return 0;
        a /= g;
        b /= g;
    
        int sign_a = a > 0 ? +1 : -1;
        int sign_b = b > 0 ? +1 : -1;
    
        shift_solution(x, y, a, b, (minx - x) / b);
        if (x < minx)
            shift_solution(x, y, a, b, sign_b);
        if (x > maxx)
            return 0;
        int lx1 = x;
    
        shift_solution(x, y, a, b, (maxx - x) / b);
        if (x > maxx)
            shift_solution(x, y, a, b, -sign_b);
        int rx1 = x;
    
        shift_solution(x, y, a, b, -(miny - y) / a);
        if (y < miny)
            shift_solution(x, y, a, b, -sign_a);
        if (y > maxy)
            return 0;
        int lx2 = x;
    
        shift_solution(x, y, a, b, -(maxy - y) / a);
        if (y > maxy)
            shift_solution(x, y, a, b, sign_a);
        int rx2 = x;
    
        if (lx2 > rx2)
            swap(lx2, rx2);
        int lx = max(lx1, lx2);
        int rx = min(rx1, rx2);
    
        if (lx > rx)
            return 0;
        return (rx - lx) / abs(b) + 1;
    }
    
  4. 找到x+y最小的解

    将通解带入 x + y x+y x+y
    x ′ + y ′ = x + y + k ⋅ ( b g − a g ) = x + y + k ⋅ b − a g x&#x27; + y&#x27; = x + y + k \cdot \left(\frac{b}{g} - \frac{a}{g}\right) = x + y + k \cdot \frac{b-a}{g} x+y=x+y+k(gbga)=x+y+kgba
    If a &lt; b a &lt; b a<b, 找最小的 k k k. If a &gt; b a &gt; b a>b, 找最大的 k k k. If a = b a = b a=b, x + y x + y x+y为定值.

例题

LightOj - Solutions to an equation

find the number of solutions of the following equation: Ax + By + C = 0 where x1 ≤ x ≤ x2 and y1 ≤ y ≤ y2.

习题

代码

//https://blog.csdn.net/yjf3151731373/article/details/70048595?locationNum=1&fps=1
LL exgcd(LL a,LL b,LL &x,LL &y)
{
    if(b==0)
    {
        x=1, y=0;
        return a;
    }
    LL ans=exgcd(b,a%b,x,y);
    LL tmp=x;
    x=y;
    y=tmp-a/b*y;
    return ans;
}
LL F(LL x)
{
    if(x==0) return 0;
    if(x>0) return 1;
    else return -1;
}
int main()
{
    int t, ncase=1;
    scanf("%d", &t);
    while(t--)
    {
        LL a, b, c, ax1,ax2, ay1, ay2;
        scanf("%lld %lld %lld %lld %lld %lld %lld", &a, &b, &c, &ax1, &ax2, &ay1, &ay2);
        printf("Case %d: ",ncase++);
        if(a==0&&b==0)
        {
            if(c==0) printf("%lld\n",(ay2-ay1+1)*(ax2-ax1+1));
            else printf("0\n");
            continue;
        }
        else if(a==0)
        {
            if(c%b!=0) printf("0\n");
            else
            {
                LL z=-c/b;
                if(z>=ay1&&z<=ay2) printf("%lld\n",(ax2-ax1+1));
                else printf("0\n");
            }
            continue;
        }
        else if(b==0)
        {
            LL z=-c/a;
            if(c%a!=0) printf("0\n");
            else if(z>=ax1&&z<=ax2) printf("%lld\n",(ay2-ay1+1));
            else printf("0\n");
            continue;
        }
        LL x, y;
        LL g=exgcd(a,b,x,y);
        if(c%g!=0) printf("0\n");
        else//加和减没有区别因为都可以改变k的值得到,注意处理不等式乘以负数的符号变换,公式中的b为b/gcd(a,b)因为这样可以扩大k值
        {
            LL k1, k2, k3, k4;
            if(b*g<0) swap(ax1,ax2);
            k1=(ax1*g+c*x)/b;
            if((ax1*g+c*x)% b!=0 && F(ax1*g+c*x)*F(b) > 0) k1+=1;
            k2=(ax2*g+c*x)/b;
            if((ax2*g+c*x)% (b)!=0 && F(ax2*g+c*x)*F(b) < 0) k2-=1;
            if(-a*g<0) swap(ay1,ay2);
            k3=(ay1*g+c*y)/(-a);
            if((ay1*g+c*y)%(-a)!=0 && F(ay1*g+c*y)*F(-a)>0) k3+=1;
            k4=(ay2*g+c*y)/(-a);
            if((ay2*g+c*y)%(-a)!=0 && F(ay2*g+c*y)*F(-a)<0) k4-=1;
            if(k3>k2||k4<k1) printf("0\n");
            else printf("%lld\n",mint(k2,k4)-maxt(k1,k3)+1);
        }
    }
    return 0;


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Best KeyBoard

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值