【扩展欧几里得】总结//poi 2002 Counting-Out Rhyme


        

              作为一个oier,居然现在才弄扩展欧几里得,以前屡屡没有遇到,去年这个时候看了一点资料,但是没有写什么题目,后来就忘记了,这次终于弄了一下。

              想想std用的居然还是一个颓颓的折半枚举>.<, 羞愧ing

              

              题目化简后变成,解一个方程组中A的最小解

                           A = k1 * d1 + b1;

                           A = k2 * d2 + b2;

                          ...

                          A  = k20 * d20 + b20; 

                      d1~20, b1~20 已知。

              首先整理一下扩展欧几里得算法(尽量详细一点):

               

               【1】 欧几里得算法: 由于gcd(a,b) = gcd(b, a%b) //a>= b, 每次递归或迭代处理。

               

               【2】 求一组解 (x,y)满足:gcd(a,b)= a * x + b * y 的一组;

                                 由于 a*x1 + b*y1 = gcd(a,b)

                                          b*x2 + a%b*y2 = gcd(b,a%b)

                                 所以 a*x1+b*y1 = b*x2 + a%b*y2  (考虑使用算数基本定理)

                                         a*x1 + b*y1 = b*x2 + a*y2 - a/b * b*y2  (“/”是指整除)

                                         合并得: a*x1 + b*y1 =a*y2  +  b* (x2 - a/b*b*y2)

                                 使用算数基本定理: x1 = y2 , y1 = x2 - a/b*b*y2;

                                 所以在回溯的时候,利用x2,y2 得到 x1, y1;

               

                【3】求x尽量小且为正整数的一组(x,y)呢?

                                   化简 a/gcd(a,b) * x  + b/gcd(a,b) * y  = 1. 现在已知一组解(x1,y1)

                                   x  每增加b/gcd(a,b), y减少a/gcd(a,b), 反之亦然。

                                   另mo = b/gcd(a,b)那么min(x) =   (x1 % mo + mo) % mo; 然后对应的y可以求出。

               

                【4】 如果求a*x + b*y =  c 的一组x最小正解呢?

                                   如果c % gcd(a,b) != 0  则无解。

                                   那么a/gcd(a,b) * x + b/ gcd(a,b) * y  = c / gcd(a,b); 两边再同时处以c/gcd(a,b),

                                   化为a*x + b*y = 1 的形式,得到一组(x1,y1), 然后x1*= c/gcd(a,b), y1 *= c/gcd(a,b),

                                   由于a/gcd(a,b) + b/ gcd(a,b) 互质,仍然可以用上面的方法求出x为最小正整数的解(x,y)

               

                【5】 回归原题,如果可以把20个式子化为1个,可以轻易地求出最小的A

                            将A = ki * di + bi,  A = kj*dj + bj合并:

                            首先:  di * ki - dj * kj = bj - bi

                                   进一步用上面的方法化成 a*x - b*y = 1  的形式,只是如果把负数递归进gcd,必定会出问题,我们可以先求出 a * x + b * y = 1的一组解,  

                            然后把y取反,在求x 的正最小解即可。

                                   用上面的方法可以解出A(ki*di + bi)的一个正最小值, 可以知道,新获得的式子必定是A  = lcm(ki , kj) *d+ b 的形式,A已知了,b自然可以得到,

                           这样两个式子就被合并成一个式子了。

                  

                 poi2002  counting-out rhyme

                 给定约瑟夫环的出圈次序,求最小的k使得报k 的人出圈,出圈顺序与输入相符。

                 可以得到关于k  对于1,2,3,....,n的余数,轻松用上面的方法合并等式,求出最小的k:

                

# include <cstdlib>
# include <cstdio>
# include <cmath>

using namespace std;

typedef long long int64;

int k, n,go[30], step[30], m[30];
int64 eb, k1, b1, d1, k2, b2, d2, k3, b3, d3, GCD;
int find(int s, int t)
{
	int now = s, ask = 0;
	for (;now !=t; )
	{
		if (++now > n) now = 1;
		if (!step[now]) ask++;
	}
	return ask;
}

void prepare()
{
	int now, i, c;
	for (now = go[1], m[n]= go[1], step[now]=true,i = n-1; i >= 1; i--)
		c = go[n-i+1], m[i] = find(now, c),now = c, step[now]=true;
	for (i = 1; i <= n; i++) m[i] %= i;
}

int64 gcd(int64 a, int64 b) {return !b?a:gcd(b,a%b);};
void ext_gcd(int64 a, int64 b, int64 &x, int64 &y)
{
	if (!b) x = 1, y = 0;
	else
	{
		ext_gcd(b, a%b, x, y);
		int64 tmp = x;
		x = y;
		y = tmp - a/b * y;
	}
}
bool update(int64 d2, int64 b2)
{
	eb = b2-b1; GCD = gcd(d1, d2);
	d1/= GCD; d2 /= GCD; 
	if (eb % GCD) return false; else eb /= GCD;
	ext_gcd(d1, d2, k1, k2);
	k2 = -k2;
	k1 *= eb; k2 *= eb;
	k1 = (k1 % d2 + d2)% d2;
	k2 = (k1 * d1 - 1) / d2;
    d3 = d1 * d2 * GCD;
    b3 = (k1 * d1 * GCD + b1) % d3;
    d1 = d3; b1 = b3;
}
int main()    
{
        int i;
	scanf("%d",&n);
	for (i = 1; i <= n; i++) 
	{
		scanf("%d", &k);
		go[k] = i;
	}
	prepare();
        d1 = 1;b1 = 0;
	for (i = 2; i <= n; i++)
	    if (!update(i,m[i])) break; 
	if (i > n) printf("%I64d", b1);
	else printf("no");
	return 0;
}

               

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值