中国余数定理(Chinese Remainder Theorem)

前言


中国余数定理也叫孙子定理记录在《孙子算经》中:“今有物不知其数,三三数之剩二(除以3余2),五五数之剩三(除以5余3),七七数之剩二(除以7余2),问物几何?”

关于孙子算经这道题的解法宋朝数学家秦九韶于1247年《数书九章》卷一、二《大衍类》对“物不知数”问题做出了完整系统的解答。明朝数学家程大位将解法编成易于上口的《孙子歌诀》:

三人同行七十稀,五树梅花廿一支,七子团圆正半月,除百零五使得知。

即:先找到5与7公倍数中余3为1的数=70,3与7公倍数中余5为1的数=21,3与5公倍数中余7为1的数=15,然后将70 * 2(除以3余2)+21 * 3(除以5余3)+15 * 2(除以7余2)=233,最后233%(3 * 5 * 7=105)=23。

接下来记录一道用到中国余数定理的题目。

题目(来源GCJ2019 1A Golf Gophers)


Problem

Last year, a bunch of pesky gophers took up residence in our orchard. We tried to change our line of work by opening up a miniature golf course, but it looks like the gophers have followed us here! Once again, we need to figure out how many gophers there are, but we cannot observe them directly because they are secretive and nocturnal, whereas we like to sleep at night. We do know there are between 1 and M gophers, inclusive.

Our mini golf course is famous for having a small electronic windmill on each of its 18 holes. The i-th windmill has 2 ≤ Bi ≤ 18 blades, which are numbered from 0 to Bi-1, clockwise. Each night, before going to sleep, we turn off the windmills and set each one such that blade 0 is pointing downward, which is important so that the windmills can charge up properly for the next day. However, we have noticed that when we wake up, the windmills have been disturbed. Since our mini golf course is in a windless area, we think the mischievous gophers must be responsible!

We know that every night, all of the gophers emerge, one by one; each of them chooses one of the windmills independently and uniformly at random and rotates it counterclockwise by one blade. So, for example, for a windmill with 3 blades for which 0 is pointing downward, the first gopher to interact with it turns it so that 1 is pointing downward, and then the next gophers to interact with that windmill make the downward-pointing blade have number 2, then 0, then 1, and so on.

We have devised a plan. We designed our windmills so that we can easily change the number of blades (to modulate the difficulty of our course), and we will now take advantage of this! Each night, before going to sleep, we can choose the number of blades on each of the 18 windmills, within the given limits; we do not have to use the same number of blades on each windmill, or make the same choices every night. In the morning, we will observe the number on each windmill's downward-pointing blade.

We have N nights in which to figure out G, the number of gophers. Can you help us?

Input and output

This is an interactive problem. You should make sure you have read the information in the Interactive Problems section of our FAQ.

Initially, your program should read a single line containing three integers T, N and M, the number of test cases, the number of nights allowed per test case and the maximum number of gophers, respectively. Then, you need to process T test cases.

In each test case, your program processes up to N + 1 exchanges with our judge. You may make up to N exchanges of the following form:

  • Your program outputs one line with eighteen integers between 2 and 18, inclusive; the i-th of these represents the number of blades you want the i-th windmill to have on that night.
  • The judge responds with one line with eighteen integers; the i-th of these represents the number on the downward-pointing blade of the i-th windmill in the morning, after the gophers have worked their mischief. If you sent invalid data (e.g., a number out of range, or a malformed line), the judge instead responds with -1.

On each night, for each gopher, the choice of which windmill the gopher turns is uniform at (pseudo)-random, and independent of any other choice by any gopher (including itself) on any night.

After making between 0 and N exchanges as explained above, you must make one more exchange of the following form:

  • Your program outputs one integer: your guess for G, the number of gophers.
  • The judge responds with one line with a single integer: 1 if your answer is correct, and -1 if it is not (or you have provided a malformed line).

After the judge sends -1 to your input stream (because of either invalid data or an incorrect answer), it will not send any other output. If your program continues to wait for the judge after receiving -1, your program will time out, resulting in a Time Limit Exceeded error. Notice that it is your responsibility to have your program exit in time to receive a Wrong Answer judgment instead of a Time Limit Exceeded error. As usual, if the memory limit is exceeded, or your program gets a runtime error, you will receive the appropriate judgment.

Limits

1 ≤ T ≤ 20.
Time limit: 20 seconds per test set.
Memory limit: 1GB.

Test set 1 (Visible)

N = 365.
M = 100.

Test set 2 (Hidden)

N = 7.
M = 106.

Testing Tool

You can use this testing tool to test locally or on our servers. To test locally, you will need to run the tool in parallel with your code; you can use our interactive runner for that. For more information, read the Interactive Problems section of the FAQ.

Local Testing Tool

To better facilitate local testing, we provide you the following script. Instructions are included inside. You are encouraged to add more test cases for better testing. Please be advised that although the testing tool is intended to simulate the judging system, it is NOT the real judging system and might behave differently.

If your code passes the testing tool but fails the real judge, please check the Coding section of our FAQ to make sure that you are using the same compiler as us.

Download testing_tool.py

Sample Interaction

This interaction corresponds to Test set 1. Suppose that, unbeknownst to us, the judge has decided that there are 10 gophers.

  t, n, m = readline_int_list()   // Reads 20 into t, 365 into n and 100 into m.
  // Choose numbers of blades for day 1.
  printline 2 2 2 2 18 3 3 3 3 3 3 4 4 4 4 5 2 2 to stdout
  flush stdout
  // Reads 0 0 0 0 0 0 1 2 1 0 1 2 0 0 0 0 1 0 into res.
  res = readline_int_list()
  // Choose numbers of blades for day 2.
  printline 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 2 to stdout
  flush stdout
  // Reads 0 1 1 2 0 0 1 0 0 0 0 0 0 1 0 0 0 0 into res.
  res = readline_int_list()
  printline 8 to stdout        // We make a wrong guess even though we could
  flush stdout                 // have investigated for up to 363 more nights.
  verdict = readline_int()     // Reads -1 into verdict (judge has decided our
                               //   solution is incorrect)
  exit                         // Exits to avoid an ambiguous TLE error

Notice that even though the guess was consistent with the information we received from the judge, we were still wrong because we did not find the correct value.

分析


题目大意是有一群囊鼠晚上出来活动,他们会拨乱我们放置的风车叶子,并且是顺时针拨乱的比如有三片标记的叶子0,1,2。一只囊鼠通过由0 ~> 1,两只囊鼠通过就0 ~> 1 ~> 2,3只就0 ~> 1 ~> 2 ~> 0,(其实就是通过囊鼠个数求叶子的余数)。并且囊鼠每晚只完全随机选择一个风车来拨动,下面希望在有限次设置风车下计算出囊鼠数量,风车数量固定18,每个风车叶子可以选择[2,18]中的任意值,并且初始都是叶子都是标记0。

首先,我们可以确定我们不能每晚风车选不同数值,像Sample Interaction一样,这样完全随机的返回18个余数无法利用。我们只能每晚所有风车选一样的叶子数。比如18个18,如果运气好(囊鼠数量很少或者很多个夜晚的话),我们有可能18个风车都没有经历一个轮回,那么所有返回数值相加就是结果。

但是很不幸,对于大数据囊鼠达到106,只有7个夜晚是不可能完成的。这就需要用到中国余数定理来解决了。

这里和孙子算经中的问题有点不同,余数数量达到了7个,但是并不影响解法原理,只需从3个余数扩展到7个。比如扩展到(3,5,7,9)四个数,对与3的余数处理,我同样找到5 * 7 * 9的公倍数中余3为1的数,然后乘以3的余数,最后把4个操作数加起来余3 * 5 * 7 * 9的值就得到结果。

然后回到题目中我们需要7个数的乘积>106就能保证最后值能覆盖106所有可能。这里选择得是{5,7,9,11,13,16,17}这7个数,当然也可以选择其他乘积更大的数,比如11到17。

最后在求多个数乘积和某个数余1的数的时候可以用到扩展欧几里德算法,公式ax=1(mod p),p是我们的选定的某个叶子数,a是其他数乘积,通过扩展欧几里德算法可以求得x(也叫乘法逆元),ax就是我们要的余p为1的值。这里不能用费马小定理的原因是我们选择的数当中9,16都不是素数。

解法代码


//
//  main.cpp
//  Golf Gophers
//
//  Created by Jiao Liu on 4/22/19.
//  Copyright © 2019 ChangHong. All rights reserved.
//

#include <iostream>

using namespace std;
int t,n,m;
int blade[7] = {5,7,9,11,13,16,17};
int tot = 5*7*9*11*13*16*17;

long long Extend_Gcd(long long a, long long b, int &x, int &y) {
    if (!b) {
        x = 1; y = 0;
        return  a;
    }
    else {
        long long r = Extend_Gcd(b, a%b, y, x);
        y -= a / b*x;
        return r;
    }
}

int main(int argc, const char * argv[]) {
    cin>>t>>n>>m;
    while (t--) {
        long long ans = 0;
        for (int d = 0; d < min(7, n); d++) {
            for (int i = 0; i < 18; i++) {
                cout<<blade[d]<<" ";
            }
            cout<<endl;
            fflush(stdout);
            long long total = 0;
            for (int i = 0; i < 18; i++) {
                int tm;
                cin>>tm;
                total += tm;
            }
            int p = tot/blade[d];
            int x,y;
            Extend_Gcd(p, blade[d], x, y);
            ans += x * total * p;
            ans %= tot;
        }
        cout<<ans%tot<<endl;
        fflush(stdout);
        int res;
        cin>>res;
        if (res != 1) {
            return 0;
        }
    }
    return 0;
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容