【vijos】1164 曹冲养猪(中国剩余定理)

https://vijos.org/p/1164

好赞orz。

对于求一组线性同余方程

x=a[i](mod m[i])

这里任意两个m[i]和m[j]都互质

那么可以用中国剩余定理来做。

对中国剩余定理的理解:(转自matrix67神犇的blog:http://www.matrix67.com/blog/archives/5100

    最后一点可能需要一些解释。让我们来举些例子。假如有 1 路和 2 路两种公交车,其中 1 路车每 6 分钟一班,2 路车每 8 分钟一班。如果你刚刚错过两路公交车同时出发的壮景,那么下一次再遇到这样的事情是多少分钟之后呢?当然, 6 × 8 = 48 分钟,这是一个正确的答案,此时 1 路公交车正好是第 8 班, 2 路公交车正好是第 6 班。不过,实际上,在第 24 分钟就已经出现了两车再次同发的情况了,此时 1 路车正好是第 4 班, 2 路车正好是第 3 班。但是,如果把例子中的 6 分钟和 8 分钟分别改成 4 分钟和 7 分钟,那么要想等到两车再次同发,等到第 4 × 7 = 28 分钟是必需的。类似的,假如某一首歌的长度正好是 6 分钟,另一首歌的长度正好是 8 分钟,让两首歌各自循环播放, 6 × 8 = 48 分钟之后你听到的“合声”将会重复,但实际上第 24 分钟就已经开始重复了。但若两首歌的长度分别是 4 分钟和 7 分钟,则必需到第 4 × 7 = 28 分钟之后才有重复,循环现象不会提前发生。

    究其原因,其实就是,对于任意两个数,两个数的乘积一定是它们的一个公倍数,但若这两个数互质,则它们的乘积一定是它们的最小公倍数。事实上,我们还能证明一个更强的结论: a 和 b 的最大公约数和最小公倍数的乘积,一定等于 a 和 b 的乘积。在第四节中,我们会给出一个证明。

    很多更复杂的数学现象也都跟互质有关。《孙子算经》卷下第二十六问:“今有物,不知其数。三、三数之,剩二;五、五数之,剩三;七、七数 之,剩二。问物几何?答曰:二十三。”翻译过来,就是有一堆东西,三个三个数余 2 ,五个五个数余 3 ,七个七个数余 2 ,问这堆东西有多少个?《孙子算经》给出的答案是 23 个。当然,这个问题还有很多其他的解。由于 105 = 3 × 5 × 7 ,因而 105 这个数被 3 除、被 5 除、被 7 除都能除尽。所以,在 23 的基础上额外加上一个 105 ,得到的 128 也是满足要求的解。当然,我们还可以在 23 的基础上加上 2 个 105 ,加上 3 个 105 ,等等,所得的数都满足要求。除了形如 23 + 105n 的数以外,还有别的解吗?没有了。事实上,不管物体总数除以 3 的余数、除以 5 的余数以及除以 7 的余数分别是多少,在 0 到 104 当中总存在唯一解;在这个解的基础上再加上 105 的整倍数后,可以得到其他所有的正整数解。后人将其表述为“中国剩余定理”:给出 m 个两两互质的整数,它们的乘积为 P ;假设有一个未知数 M ,如果我们已知 M 分别除以这 m 个数所得的余数,那么在 0 到 P – 1 的范围内,我们可以唯一地确定这个 M 。这可以看作是 M 的一个特解。其他所有满足要求的 M ,则正好是那些除以 P 之后余数等于这个特解的数。注意,除数互质的条件是必需的,否则结论就不成立了。比如说,在 0 到 7 的范围内,除以 4 余 1 并且除以 2 也余 1 的数有 2 个,除以 4 余 1 并且除以 2 余 0 的数则一个也没有。

    从某种角度来说,中国剩余定理几乎是显然的。让我们以两个除数的情况为例,来说明中国剩余定理背后的直觉吧。假设两个除数分别是 4 和 7 。下表显示的就是各自然数除以 4 和除以 7 的余数情况,其中 x mod y 表示 x 除以 y 的余数,这个记号后面还会用到。

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
i mod 4 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3
i mod 7 0 1 2 3 4 5 6 0 1 2 3 4 5 6 0 1 2 3 4 5
i 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
i mod 4 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3
i mod 7 6 0 1 2 3 4 5 6 0 1 2 3 4 5 6 0 1 2 3 4

    i mod 4 的值显然是以 4 为周期在循环, i mod 7 的值显然是以 7 为周期在循环。由于 4 和 7 是互质的,它们的最小公倍数是 4 × 7 = 28 ,因而 (i mod 4, i mod 7) 的循环周期是 28 ,不会更短。因此,当 i 从 0 增加到 27 时, (i mod 4, i mod 7) 的值始终没有出现重复。但是, (i mod 4, i mod 7) 也就只有 4 × 7 = 28 种不同的取值,因而它们正好既无重复又无遗漏地分给了 0 到 27 之间的数。这说明,每个特定的余数组合都在前 28 项中出现过,并且都只出现过一次。在此之后,余数组合将产生长度为 28 的循环,于是每个特定的余数组合都将会以 28 为周期重复出现。这正是中国剩余定理的内容。

    中国剩余定理有很多漂亮的应用,这里我想说一个我最喜欢的。设想这样一个场景:总部打算把一份秘密文件发送给 5 名特工,但直接把文件原封不动地发给每个人,很难保障安全性。万一有特工背叛或者被捕,把秘密泄露给了敌人怎么办?于是就有了电影和小说中经常出现的情 节:把绝密文件拆成 5 份, 5 名特工各自只持有文件的 1/5 。不过,原来的问题并没有彻底解决,我们只能祈祷坏人窃取到的并不是最关键的文件片段。因此,更好的做法是对原文件进行加密,每名特工只持有密码的 1/5 ,这 5 名特工需要同时在场才能获取文件全文。但这也有一个隐患:如果真的有特工被抓了,当坏人们发现只拿到其中一份密码没有任何用处的同时,特工们也会因为少一 份密码无法解开全文而烦恼。此时,你或许会想,是否有什么办法能够让特工们仍然可以恢复原文,即使一部分特工被抓住了?换句话说,有没有什么密文发布方 式,使得只要 5 个人中半数以上的人在场就可以解开绝密文件?这样的话,坏人必须要能操纵半数以上的特工才可能对秘密文件造成实质性的影响。这种秘密共享方式被称为 (3, 5) 门限方案,意即 5 个人中至少 3 人在场才能解开密文。

    利用中国剩余定理,我们可以得到一种巧妙的方案。回想中国剩余定理的内容:给定 m 个两两互质的整数,它们的乘积为 P ;假设有一个未知数 M ,如果我们已知 M 分别除以这 m 个数所得的余数,那么在 0 到 P – 1 的范围内,我们可以唯一地确定这个 M 。我们可以想办法构造这样一种情况, n 个数之中任意 m 个的乘积都比 M 大,但是任意 m – 1 个数的乘积就比 M 小。这样,任意 m 个除数就能唯一地确定 M ,但 m – 1 个数就不足以求出 M 来。 Mignotte 门限方案就用到了这样一个思路。我们选取 n 个两两互质的数,使得最小的 m 个数的乘积比最大的 m – 1 个数的乘积还大。例如,在 (3, 5) 门限方案中,我们可以取 53 、 59 、 64 、 67 、 71 这 5 个数,前面 3 个数乘起来得 200128 ,而后面两个数相乘才得 4757 。我们把文件的密码设为一个 4757 和 200128 之间的整数,比如 123456 。分别算出 123456 除以上面那 5 个数的余数,得到 19 、 28 、 0 、 42 、 58 。然后,把 (53, 19) 、 (59, 28) 、 (64, 0) 、 (67, 42) 、 (71, 58) 分别告诉 5 名特工,也就是说特工 1 只知道密码除以 53 余 19 ,特工 2 只知道密码除以 59 余 28 ,等等。这样,根据中国剩余定理,任意 3 名特工碰头后就可以唯一地确定出 123456 ,但根据 2 名特工手中的信息只能得到成百上千个不定解。例如,假设我们知道了 x 除以 59 余 28 ,也知道了 x 除以 67 余 42 ,那么我们只能确定在 0 和 59 × 67 – 1 之间有一个解 913 ,在 913 的基础上加上 59 × 67 的整倍数,可以得到其他满足要求的 x ,而真正的 M 则可以是其中的任意一个数。

这些例子通俗易懂,十分赞。

然后是中国剩余定理的解决:

    最后,让我们把上一节留下的一点悬念给补完:怎样求解《孙子算经》中的“今有物,不知其数”一题。已知有一堆东西,三个三个数余 2 ,五个五个数余 3 ,七个七个数余 2 ,问这堆东西有多少个?根据中国剩余定理,由于除数 3 、 5 、 7 两两互质,因而在 0 到 104 之间,该问题有唯一的答案。我们求解的基本思路就是,依次找出满足每个条件,但是又不会破坏掉其他条件的数。我们首先要寻找一个数,它既是 5 的倍数,又是 7 的倍数,同时除以 3 正好余 2 。这相当于是在问, 35 的多少倍除以 3 将会余 2 。于是,我们利用扩展的辗转相除法求解方程 35x mod 3 = 2 。这个方程是一定有解的,因为 5 和 3 、 7 和 3 都是互质的,从而 5 × 7 和 3 也是互质的(到了下一节,这一点会变得很显然)。解这个方程可得 x = 1 。于是, 35 就是我们要找到的数。第二步,是寻找这么一个数,它既是 3 的倍数,又是 7 的倍数,同时除以 5 余 3 。这相当于求解方程 21x mod 5 = 3 ,根据和刚才相同的道理,这个方程一定有解。可以解得 x = 3 ,因此我们要找的数就是 63 。最后,我们需要寻找一个数,它能同时被 3 和 5 整除,但被 7 除余 2 。这相当于求解方程 15x mod 7 = 2 ,解得 x = 2 。我们想要找的数就是 30 。现在,如果我们把 35 、 63 和 30 这三个数加在一起会怎么样?它将会同时满足题目当中的三个条件!它满足“三个三个数余 2 ”,因为 35 除以 3 是余 2 的,而后面两个数都是 3 的整倍数,所以加在一起后除以 3 仍然余 2 。类似地,它满足“五个五个数余 3 ”,因为 63 除以 5 余 3 ,另外两个数都是 5 的倍数。类似地,它也满足“七个七个数余 2 ”,因而它就是原问题的一个解。你可以验证一下, 35 + 63 + 30 = 128 ,它确实满足题目的所有要求!为了得出一个 0 到 104 之间的解,我们在 128 的基础上减去一个 105 ,于是正好得到《孙子算经》当中给出的答案, 23 。

然后模板我是参照了quartergeek神犇和zky神犇的。(三个版本,,sad)

但是我有点疑问,假设这样做,x可能为负数,(当然可以将它变成正的),这样会不会影响到结果?(虽然在本题这样写可以A,但是不知道是不是数据弱。。)

ll china(ll* m, ll* a) {
	ll M=1, d, x, y, ret=0;
	for1(i, 1, n) M*=m[i];
	for1(i, 1, n) {
		ll w=M/m[i];
		exgcd(w, m[i], d, x, y);
		ret=(ret+w*x*a[i])%M;
	}
	ret=(ret+M)%M;
	return ret;
}

先这样写吧。。

(我觉得这样写似乎会错,放上另一个模板,就是将位置调换了一下)

ll china(ll* m, ll* a) {
	ll M=1, d, x, y, ret=0;
	for1(i, 1, n) M*=m[i];
	for1(i, 1, n) {
		ll w=M/m[i];
		exgcd(m[i], w, d, x, y);
		ret=(ret+w*y*a[i])%M;
	}
	ret=(ret+M)%M;
	return ret;
}

反正如果考到好好思考一下应该可以的。

zky神犇是用y来解决的,y肯定是正的

 

那么本题就是模板题了。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
#define rep(i, n) for(int i=0; i<(n); ++i)
#define for1(i,a,n) for(int i=(a);i<=(n);++i)
#define for2(i,a,n) for(int i=(a);i<(n);++i)
#define for3(i,a,n) for(int i=(a);i>=(n);--i)
#define for4(i,a,n) for(int i=(a);i>(n);--i)
#define CC(i,a) memset(i,a,sizeof(i))
#define read(a) a=getint()
#define print(a) printf("%d", a)
#define dbg(x) cout << (#x) << " = " << (x) << endl
#define printarr2(a, b, c) for1(_, 1, b) { for1(__, 1, c) cout << a[_][__]; cout << endl; }
#define printarr1(a, b) for1(_, 1, b) cout << a[_] << '\t'; cout << endl
inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<'0'||c>'9'; c=getchar()) if(c=='-') k=-1; for(; c>='0'&&c<='9'; c=getchar()) r=r*10+c-'0'; return k*r; }
inline const int max(const int &a, const int &b) { return a>b?a:b; }
inline const int min(const int &a, const int &b) { return a<b?a:b; }

typedef long long ll;
const int N=15;
ll a[N], b[N], n;
void exgcd(ll a, ll b, ll &d, ll &x, ll &y) {
	if(!b) { d=a; x=1; y=0; return; }
	exgcd(b, a%b, d, y, x); y-=a/b*x;
}
ll china(ll* m, ll* a) {
	ll M=1, d, x, y, ret=0;
	for1(i, 1, n) M*=m[i];
	for1(i, 1, n) {
		ll w=M/m[i];
		exgcd(w, m[i], d, x, y);
		ret=(ret+w*x*a[i])%M;
	}
	ret=(ret+M)%M;
	return ret;
}
int main() {
	read(n);
	for1(i, 1, n) read(a[i]), read(b[i]);
	printf("%lld\n", china(a, b));
	return 0;
}

 

2015.4.19 upd:大家请上wiki。。。。即看下边这张图= =(你们就知道我是不是很傻叉啊。。

【vijos】1164 曹冲养猪(中国剩余定理)_第1张图片 

 


 

 

描述

自从曹冲搞定了大象以后,曹操就开始捉摸让儿子干些事业,于是派他到中原养猪场养 猪,可是曹冲满不高兴,于是在工作中马马虎虎,有一次曹操想知道母猪的数量,于是曹冲想狠狠耍曹操一把。举个例子,假如有16头母猪,如果建了3个猪圈, 剩下1头猪就没有地方安家了。如果建造了5个猪圈,但是仍然有1头猪没有地方去,然后如果建造了7个猪圈,还有2头没有地方去。你作为曹总的私人秘书理所 当然要将准确的猪数报给曹总,你该怎么办?

格式

输入格式

第一行包含一个整数n (n <= 10) – 建立猪圈的次数,解下来n行,每行两个整数ai, bi( bi <= ai <= 1000), 表示建立了ai个猪圈,有bi头猪没有去处。你可以假定ai,aj互质.

输出格式

输出包含一个正整数,即为曹冲至少养母猪的数目。

样例1

样例输入1[复制]

3
3 1
5 1
7 2

样例输出1[复制]

16

来源

huyichen

你可能感兴趣的:(OS)