2020牛客暑期多校训练营(第三场)F.Fraction Construction Problem(扩展欧几里得) 一个详细的题解

题目大意:
给两个正整数a和b;
要求找到四个正整数c,d,e,f,满足 a b = c d − e f \frac{a}{b}=\frac{c}{d}-\frac{e}{f} ba=dcfe

思路:
预处理
先init预处理出1到MAXN每个数字的最小质因子

第一种情况,如果 g c d ( a , b ) ≠ 1 gcd(a,b)\not = 1 gcd(a,b)=1
即a/b是可以化简的
直接有
a b = ⌊ a b ⌋ 1 − b − a b \frac{a}{b}=\frac{\lfloor \frac{a}{b} \rfloor}{1}-\frac{b-a}{b} ba=1babba
这第一种情况是比较好理解的。

第二种情况
处理完第一种情况后,如果b只有一个或者没有真因子,如8,9,16,25之类的数字,则无解。

第三种情况
找到b的两个互质且相乘结果为b的因子:
因为之前预处理过每个数的最小真因子在yz[b]数组中,这个最小真因子显然一定是质的。
我们设d=yz[b],f=b/yz[b],使b=d*f得到满足
然后为了满足第二个条件d与f互质,根据唯一分解定理,我们只需要把f中所有的yz[b]都移动到d上即可。

	d = yz[b];
	f = b/yz[b];
	while(f%yz[b]==0){//去除f中所有的yz[b]
		d*=yz[b];//移动到d身上
		f/=yz[b];
	}

题目要求
a b = c d − e f \frac{a}{b}=\frac{c}{d}-\frac{e}{f} ba=dcfe
合并两个分数
a b = c f − d e d f \frac{a}{b} = \frac{cf-de}{df} ba=dfcfde
移项
d f b = c f − d e a \frac{df}{b} = \frac{cf-de}{a} bdf=acfde
我们又知道df=b,所以左边可以变成1
1 = f ⋅ ( c a ) − d ⋅ ( e a ) 1 = f \cdot (\frac{c}{a}) - d \cdot (\frac{e}{a}) 1=f(ac)d(ae)
设x=c/a;y=-e/a则有
1 = f ⋅ x + d ⋅ y 1 = f \cdot x + d \cdot y 1=fx+dy
这时候就可以想到我们可以用扩展欧几里得了

	Exgcd(f,d,x,y);
	while(x<=0||y>=0){//使x>0,y<0 
		x+=d;y-=f;
	}
	c = x*a; e = -y*a;

在得到x和y之后,我们要找到一组x>0且y<0的解。
有一组解怎么找其他解可以看这个帖子https://www.cnblogs.com/Antigonae/p/10106068.html

AC代码
(是补的,比赛的时候我跟个傻X一样)

#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;

template<class T>inline void read(T &x){x=0;char o,f=1;while(o=getchar(),o<48)if(o==45)f=-f;do x=(x<<3)+(x<<1)+(o^48);while(o=getchar(),o>47);x*=f;}
//int cansel_sync=(ios::sync_with_stdio(0),cin.tie(0),0);
//多谢cc0408大哥,之前我开了同步流还用printf,其实是有问题的,感谢大哥指出
#define ll long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repb(i,a,b) for(int i=(a);i>=b;i--)
#define INF 0x3f3f3f3f
#define cendl printf("\n")
ll gcd(ll a,ll b){ while(b^=a^=b^=a%=b); return a; }
//#define INF 0x7fffffff

ll Exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){x=1;y=0;return a;}//边界条件结束递归 
	ll d = Exgcd(b,a%b,x,y);//gcd 
	ll t = x;
	x=y;y=t-(a/b)*y;//通过x2y2求得x1y1,层层返回 
	return d;
}

const int MAXN = 2e6+5;
int yz[MAXN];

void init(){
	int sqr = sqrt(MAXN);
	rep(i,1,MAXN-1) yz[i] = 0;
	for(int i=2;i<=sqr;i++){
		if(yz[i])continue;
		for(int j=2;i*j<MAXN;j++){
			if(yz[i*j]==0) yz[i*j]=i;
		}
	}
}

ll a,b,c,d,e,f;

void solve(){
	cin>>a>>b;
	int gcdd = gcd(a,b);
	a/=gcdd; b/=gcdd;
	int qm = a/b;//把分数化成 qm + a/b的形式 
	a%=b;
	if(gcdd!=1){//第一种情况
		printf("%d %d %d %d\n",qm+1,1,b-a,b);
		return;
	}
	if(!yz[b]){
		printf("-1 -1 -1 -1\n");
		return;
	}
	d = yz[b];
	f = b/yz[b];
	while(f%yz[b]==0){//去除f中所有的yz[b]
		d*=yz[b];//移动到b身上
		f/=yz[b];
	}
	if(f==1){printf("-1 -1 -1 -1\n");return;}//单个真因子的情况(第二种情况) 
	ll x,y;
	Exgcd(f,d,x,y);
	while(x<=0||y>=0){//使x>0,y<0 
		x+=d;y-=f;
	}
	c = x*a; e = -y*a;
	printf("%lld %lld %lld %lld\n",qm*d+c,d,e,f);
}

int main(){
	init();
	int z;
	cin>>z;
	while(z--) solve();
}

你可能感兴趣的:(牛客多校,数论,acm竞赛)