【题解】洛谷P2152 [SDOI2009] SuperGCD(高精 gcd)

12 篇文章 0 订阅
5 篇文章 0 订阅

10^10000,这个数据范围是一定得用高精度的。。不过如果用平常递归求最大公约数的算法肯定会栈溢出,除法的高精又麻烦,所以我们可以考虑减法(虽然有人说更相减损术在这道题里其实是不成立的 但可以通过)。

我们读入字符串,将对应的位存到数组里,手写高精度减法、高精度比较函数(因为gcd里我要让较大的数在前面)、输出函数,然后就要写gcd了。在没有任何优化的情况下,更相减损术的意思就是gcd(a,b)=gcd(a,a-b) (a>=a-b) 而其终止条件就是a==b,所以我们在高精度下的gcd里循环,当两个数不相等时,如果大小对应位置不一样就交换,然后让大的减去小的,再比较……最后就能得到正确的答案了。交上代码,写对的话会TLE 4个点

那么我们就得考虑如何优化了。这道题的数据还不是那么可怕,所以我们有两种优化思路(采取任何一种都行,或者你两种都采取就更好了)。

一、压位

我们知道自己开的数组里每一位存的就是一位数,实际上数组的一个位置可以存好多位,之前那种方法会造成浪费,所以我们要在读入字符串时就进行压位。因为我是倒着来存的,所以我的写法可能比较麻烦。具体的实现看代码也能看懂,总之最后我们得到了压位后的数组(举个例子 12345678988  压位过后就变成a.num[0]=45678988 a.num[1]=123 a.tot=2),这样不仅节省了空间,在时间复杂度上也大大节省了。压了位后,我们要处理的就是减法运算了。由于压位对比较函数的执行是没有影响的,所以我们只需要考虑减法,这里就是从低位往高位减,如果减出了负数,这里注意要让数组的下一位减1,这一位加上1e8。最后清掉高位上的零即可。还要注意的一点是,这么操作过后最后输出答案时有些数组的位置里的数不一定是八位的 举个例子 8974634 3 对应308974634,这个时候我们就需要在输出里判断,如果是最高位的话不够就跳过,如果不是最高位不够就补齐,%08d代表在数左边填上0,补到八位为止。经过压位后我们就能过掉这道题了。但仔细想想,有数据还是能卡掉这种做法的 比如1e9 1,这样更相减损一定会超时,不过也没这种数据(比赛哪敢给你出这种数据啊)、所以我们这道题也就过了(后面发的代码就是这种做法)。

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
struct gj
{
	int num[2010];
	int tot;
};
void read(gj &a)
{
	string s;
	cin>>s;
	int y=0;
	int cnt=0,ans=0,t=1;
	for(int i=s.size()-1;i>=0;i--)
	{
		cnt++;
		if(cnt<=8)
		{
			ans+=t*(s[i]-'0');
			t*=10;
			continue;
		}
		a.num[y]=ans;
		cnt=1;
		t=10;
		ans=s[i]-'0';
		y++;
	}
	a.num[y]=ans;
	cnt=0;
	ans=0;
	t=1;
	y++;
	a.tot=(s.size()+7)/8;
}
void print(gj a)
{
	for(int i=a.tot-1;i>=0;i--)
	{
		if(i==a.tot-1) printf("%d",a.num[i]);
		else printf("%08d",a.num[i]);
	}
} 
void ceshi(gj a)
{
	for(int i=a.tot-1;i>=0;i--)
	{
		printf("%d",a.num[i]);
	}
}
void jian(gj &a,gj &b)
{
//	int i=a.tot-1;
//	while(i>=0)
//	{
//		a.num[i]-=b.num[i];
//		if(a.num[i]<0)
//		{
//			int j=i;
//			while(a.num[j]<0)
//			{
//				a.num[++j]-=1;
//				a.num[j-1]+=100000000;
//			}
//		}
//		i--;
//	}
	for(int i=0;i<=a.tot-1;i++)
	{
		a.num[i]-=b.num[i];
		if(a.num[i]<0)
		{
			a.num[i]+=1e8;
			a.num[i+1]-=1;
		}
	}
	while(a.num[a.tot-1]==0)
	{
		a.tot--;
	}
//	i=a.tot-1;
//	while(i>=0)
//	{
//		if(a.num[i]!=0) break;
//		else a.tot--;
//		i--;
//	}
}
int comp(gj a,gj b)
{
	if(a.tot>b.tot) return 1;
	else if(a.tot<b.tot) return 2;
	else
	{
		int i=a.tot-1;
		while(i>=0)
		{
			if(a.num[i]>b.num[i]) return 1;
			else if(a.num[i]<b.num[i]) return 2;
			i--;
		}
		return 0;
	}
}
gj gcd(gj a,gj b)
{
	int x=comp(a,b);
	while(x!=0)
	{
		gj c;
		if(x==2)
		{
			c=a;
			a=b;
			b=c;	
		} 
		jian(a,b);
//		ceshi(a);
//		cout<<' ';
//		ceshi(b);
//		cout<<endl;
//		system("Pause");
		x=comp(a,b);
	}
	return a;
}
int main()
{
//	freopen("data.in","r",stdin);
//	freopen("test.out","w",stdout);
	gj a,b;
	read(a);
	read(b);
	print(gcd(a,b));
	fclose(stdin);
	fclose(stdout);
	return 0;
}

二、简化更相减损术过程

回到刚才我们提出的问题,出现那种情况该怎么办呢?我们可以判断一下,如果两个数都是偶数,那么答案就是2*gcd(a/2,b/2),如果一个数是偶数(我们令a为偶数),那么答案就是gcd(a/2,b),如果两个数都是奇数,那我们才执行更相减损。这样可以大大减少时间复杂度,而且不会被卡掉,甚至不需要压位就能通过。那么具体的问题就在于怎么写。高精度出发我们是不会的,不过除以2还是比较简单,这里给个这种做法的链接https://www.luogu.org/blog/DEDSECspace/solution-p2152

三、前两种方法组合

这是这道题的正解,也将时间由3000ms降低到不到1000ms。

下附代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
struct gj
{
	int num[2010];
	int tot;
};
void read(gj &a)
{
	string s;
	cin>>s;
	int y=0;
	int cnt=0,ans=0,t=1;
	for(int i=s.size()-1;i>=0;i--)
	{
		cnt++;
		if(cnt<=8)
		{
			ans+=t*(s[i]-'0');
			t*=10;
			continue;
		}
		a.num[y]=ans;
		cnt=1;
		t=10;
		ans=s[i]-'0';
		y++;
	}
	a.num[y]=ans;
	cnt=0;
	ans=0;
	t=1;
	y++;
	a.tot=(s.size()+7)/8;
}
void print(gj a)
{
	for(int i=a.tot-1;i>=0;i--)
	{
		if(i==a.tot-1) printf("%d",a.num[i]);
		else printf("%08d",a.num[i]);
	}
} 
void ceshi(gj a)
{
	for(int i=a.tot-1;i>=0;i--)
	{
		printf("%d",a.num[i]);
	}
}
void chu(gj &a)
{
	int i=a.tot-1;
	while(i>=0)
	{
		if(i!=0) a.num[i-1]+=(a.num[i]%2)*100000000;
		a.num[i]/=2;
		i--;
	}
	if(a.num[a.tot-1]==0) a.tot--;
}
void cheng(gj &a)
{
	int i=0;
	while(i<a.tot)
	{
		a.num[i]*=2;
		i++;
	}
	i=0;
	while(i<a.tot)
	{
		a.num[i+1]+=a.num[i]/(100000000);
		a.num[i]=a.num[i]%(100000000);
		i++;
	}
	if(a.num[a.tot]!=0) a.tot++;
}
void jian(gj &a,gj &b)
{
	for(int i=0;i<=a.tot-1;i++)
	{
		a.num[i]-=b.num[i];
		if(a.num[i]<0)
		{
			a.num[i]+=1e8;
			a.num[i+1]-=1;
		}
	}
	while(a.num[a.tot-1]==0)
	{
		a.tot--;
	}
}
bool pd(gj a)
{
	if(a.num[0]%2==0) return 1;
	return 0;
}
int comp(gj a,gj b)
{
	if(a.tot>b.tot) return 1;
	else if(a.tot<b.tot) return 2;
	else
	{
		int i=a.tot-1;
		while(i>=0)
		{
			if(a.num[i]>b.num[i]) return 1;
			else if(a.num[i]<b.num[i]) return 2;
			i--;
		}
		return 0;
	}
}
gj gcd(gj a,gj b)
{
//	ceshi(a);
//	cout<<' ';
//	ceshi(b);
//	cout<<endl;
	int x=comp(a,b),aa=pd(a),bb=pd(b);
	int t=0;
	while(x!=0)
	{
		gj c;
		if(x==2)
		{
			c=a;
			a=b;
			b=c;
			aa=pd(a);
			bb=pd(b);	
		} 
		if(aa==1&&bb==1)
		{
			chu(a);
			chu(b);
		//	cout<<a.num[0]<<' '<<b.num[0]<<endl;
			t++;
		}
		if(aa==1&&bb!=1)
		{
			chu(a);
		}
		if(aa!=1&&bb==1)
		{
			chu(b);
		}
		if(aa!=1&&bb!=1)
		{
			jian(a,b);
		}
//		ceshi(a);
//		cout<<' ';
//		ceshi(b);
//		cout<<endl;
//		cout<<a.num[0]<<' '<<b.num[0]<<endl;
//		system("Pause");
		x=comp(a,b);
		aa=pd(a);
		bb=pd(b);
//		cout<<aa<<' '<<bb<<endl;
	}
	if(t!=0)
	{
		while(t--)
		{
			cheng(a);
		}
	}
	return a;
}
int main()
{
//	freopen("data.in","r",stdin);
//	freopen("test.out","w",stdout);
	gj a,b;
	read(a);
	read(b);
	print(gcd(a,b));
	fclose(stdin);
	fclose(stdout);
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值