• 洛谷P5656 【模板】二元一次不定方程(exgcd)


    题目传送门

    这是一道模板题(bushi)

    首先,说实话,我感觉做这道题比较吃力,毕竟我是刚刚背过(exgcd)代码,并且只是小小的对找正整数解有所了解,所以写了半天才做出来。做这种数论题确实是对计算能力和逻辑思维的考验,而且从难度上看,我这个蒟蒻能做出绿题,也有点小小的成就感。

    今天开始停课备战(CSP),下午在机房打板子,打到(exgcd),突然发现我板子题还没(A),其他的拓展题倒是做了不少,遂开始切板子题

    思路

    其实思路非常简单,就是代码实现细节真的比较多(而且我貌似写的很麻烦,写了足足有130多行)。首先用真板子来求出一组特解,然后判断是否有解。在找最小整数解的时候,要特判一波:如果有一组解能满足(x)(y)都是正整数,那么就要输出5个数。反之,则只需要输出(x)(y)的最小整数解。

    那么首先最好写的就是求出的特解都是正整数的情况,只需要用一波取模的常规操作求出一个最小正整数解,那么同时另一个解就是最大正整数解。即(x)最小时,(y)最大。就这样求出四个最值。个数可以通过最大解和最小解中间差了几个(a/d)(b/d)即可(原理就是求最小正整数解的操作,详情可见【青蛙的约会】)。然后中间稍微手玩一下,就能找到个数与最值之间的关系。

                            ll x1=(x1%(b/d)+(b/d))%(b/d);
    			ll y1=(y1%(a/d)+(a/d))%(a/d);//求出最小值
    			if(x1==0){//特判,必须是正整数
    				x1=b/d;
    			}
    			if(y1==0){
    				y1=a/d;
    			}
    			ll x2=(c-y1*b)/a;
    			ll y2=(c-x1*a)/b;//根据最小值推出最大值
    			ll sum;
    			if(x2%(b/d)==0){//手玩即可找到规律,推导为小学数学水平
    				sum=x2/(b/d);
    				x1=a/d;
    			}
    			else{
    				sum=x2/(b/d)+1;
    				x1=x2%(b/d);
    			}
    

    那么接下来就是(x)(y)这一组特解中有一个为非正整数的情况了,虽然这种情况相对复杂,但是原理是一样的。假设(x)是负的,那么(y)必然是正的,否则不可能得到正数结果。那么我们就让(x)不断地加上(b/d),为保持等式成立,也要让(y)同时减去(a/d)。这样将(x)变为正数。如果(x)变为正数后(y)变为了负数,那么就说明不可能有正整数解,这时候只要进行一步常规取模操作,输出最小正整数解即可。但是如果(x)变为正数后(y)依然是正数,就相当于变回了第一种情况,再操作即可。

    思路就是这么简单,但是代码实现的确有难度。首先我们思考(x)需要几个(b/d)才能变成正数,首先可以将(x)变为相反数,便于我们进行除法。由于除法是向下取整,所以我们用(x)除以(b/d)还要再加一个(b/d)才行(即使整除也要加,因为0并不是正整数)。所以我们就可以用(x/(b/d)+1)来求得加的(b/d)的个数,同时(y)也要减。然后再按照上述思路判断即可。

                                    if(x<=0){
    				x=-x;
    				int k=x/(b/d)+1;
    				x-=k*(b/d);
    				x=-x;
    				y-=k*(a/d);
    				if(y>0){//有正整数解 
    					ll x1=x,y2=y,sum,y1;
    					if(y2%(a/d)==0){
    						sum=y2/(a/d);
    						y1=a/d;
    					}
    					else{
    						sum=y2/(a/d)+1;
    						y1=y2%(a/d);
    					}
    					ll x2=(c-y1*b)/a;
    					printf("%lld %lld %lld %lld %lld
    ",sum,x1,y1,x2,y2);
    					continue;
    				}
    				else{//无正整数解 
    					ll x1=(xx%(b/d)+(b/d))%(b/d);
    					ll y1=(yy%(a/d)+(a/d))%(a/d);
    					if(x1==0){
    						x1=b/d;
    					}
    					if(y1==0){
    						y1=a/d;
    					}
    					printf("%lld %lld
    ",x1,y1);
    					continue;
    				}
    			}
    

    做完了

    代码

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cmath>
    #include<cstdlib>
    #include<ctime>
    #include<cstring>
    #include<queue>
    using namespace std;
    typedef long long ll;
    int T;
    inline ll read(){
    	ll x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    	return x*f;
    }
    ll exgcd(ll a,ll b,ll &x,ll &y){
    	if(b==0){
    		x=1;y=0;
    		return a;
    	}
    	ll d=exgcd(b,a%b,x,y);
    	ll z=x;x=y;y=z-y*(a/b);
    	return d;
    } 
    int main()
    {
    	scanf("%d",&T);
    	while(T--){
    		ll a,b,c,x,y;
    		a=read();b=read();c=read();
    		ll d=exgcd(a,b,x,y);
    		if(c%d!=0){
    			printf("-1
    ");
    			continue;
    		}
    		else{
    			x*=c/d,y*=c/d;
    			ll xx=x,yy=y;
    			if(x<=0){
    				x=-x;
    				int k=x/(b/d)+1;
    				x-=k*(b/d);
    				x=-x;
    				y-=k*(a/d);
    				if(y>0){//有正整数解 
    					ll x1=x,y2=y,sum,y1;
    					if(y2%(a/d)==0){
    						sum=y2/(a/d);
    						y1=a/d;
    					}
    					else{
    						sum=y2/(a/d)+1;
    						y1=y2%(a/d);
    					}
    					ll x2=(c-y1*b)/a;
    					printf("%lld %lld %lld %lld %lld
    ",sum,x1,y1,x2,y2);
    					continue;
    				}
    				else{//无正整数解 
    					ll x1=(xx%(b/d)+(b/d))%(b/d);
    					ll y1=(yy%(a/d)+(a/d))%(a/d);
    					if(x1==0){
    						x1=b/d;
    					}
    					if(y1==0){
    						y1=a/d;
    					}
    					printf("%lld %lld
    ",x1,y1);
    					continue;
    				}
    			}
    			if(y<=0){
    				y=-y;
    				int k=y/(a/d)+1;
    				y-=k*(a/d);
    				y=-y;
    				x-=k*(b/d);
    				if(x>0){//有正整数解 (y为最小整数解 
    					ll y1=y,x2=x,x1,sum;
    					if(x2%(b/d)==0){
    						sum=x2/(b/d);
    						x1=b/d;
    					}
    					else{
    						sum=x2/(b/d)+1;
    						x1=x2%(b/d);
    					}
    					ll y2=(c-x1*a)/b;
    					printf("%lld %lld %lld %lld %lld
    ",sum,x1,y1,x2,y2);
    					continue;
    				}
    				else{
    					ll x1=(xx%(b/d)+(b/d))%(b/d);
    					ll y1=(yy%(a/d)+(a/d))%(a/d);
    					if(x1==0){
    						x1=b/d;
    					}
    					if(y1==0){
    						y1=a/d;
    					}
    					printf("%lld %lld
    ",x1,y1);
    					continue;
    				}
    			}
    			ll x1=(x1%(b/d)+(b/d))%(b/d);
    			ll y1=(y1%(a/d)+(a/d))%(a/d);
    			if(x1==0){
    				x1=b/d;
    			}
    			if(y1==0){
    				y1=a/d;
    			}
    			ll x2=(c-y1*b)/a;
    			ll y2=(c-x1*a)/b;
    			ll sum;
    			if(x2%(b/d)==0){
    				sum=x2/(b/d);
    				x1=a/d;
    			}
    			else{
    				sum=x2/(b/d)+1;
    				x1=x2%(b/d);
    			}
    		}
    	}
    	return 0;
    }
    

    中间出了两个小锅,耗费了我大量时间:一个是我忘记判x>0&&y>0的情况了,可能是因为这种情况相比于其它的情况太水了,所以忘记了。第二个,由于y<0和x<0的代码绝大部分都是一样的,只不过需要该两个变量名,所以我直接复制粘贴之后一个个改变量名。结果漏改了一个,导致出锅。

    警示:这告诉我,在做这种思维量比较大的题时,一定要将思路写下来,不然很容易漏情况或者是写挂。而且在复制粘贴相似部分代码的时候,一定一定要记得改完变量名,最好是不要复制,重新打一遍,这样基本能保证不会出错。

    掌握知识点的确是要靠做题的,记得我初学搜索与回溯,也就是现在用来打暴力的神器,死活就是学不会,我就是不明白它是怎么递归的,我就是不明白为什么要回溯。但是当我抄上几篇dfs题解,找到感觉后,我独立切掉了一道同样让我耗费了大量精力的【数独】。虽然过程很难,但当切掉一道标志性题目的时候,我仿佛对这个知识点的掌握就上升了一个档次。相似的知识点和题目还有很多:比如线段树——【方差】;区间dp——【能量项链】;倍增——【严格次小生成树】;矩阵快速幂——【斐波那契数列】......这些我切过后就掌握住知识点的题,都深深印在我的脑海里。确确实实验证了眼高手低这一亘古不变的道理。而今天,切掉这道板子题,写这篇博客,是为了记录我学会了(exgcd),也是对学习之旅中的经验总结。

  • 相关阅读:
    Java VS .NET:Java与.NET的特点对比
    .NET Core 快速入门教程
    System.Net.Mail.SmtpClient通过SSL/TLS协议发送邮件失败问题解决
    在CentOS 7上安装&配置PostgreSQL 12
    如何在团队中做好Code Review
    SonarQube部署及代码质量扫描入门教程
    Kafka集群部署指南
    ASP.NET Core 入门教程 10、ASP.NET Core 日志记录(NLog)入门
    ASP.NET Core 入门教程 9、ASP.NET Core 中间件(Middleware)入门
    ASP.NET Core 入门教程 8、ASP.NET Core + Entity Framework Core 数据访问入门
  • 原文地址:https://www.cnblogs.com/57xmz/p/13927617.html
Copyright © 2020-2023  润新知