Code Feat(中国剩余定理&&枚举)

题目链接
Hooray! Agent Bauer has shot the terrorists, blown up the bad guy base, saved the hostages, exposed
the moles in the government, prevented an environmental catastrophe, and found homes for three
orphaned kittens, all in the span of 19 consecutive hours. But now, he only has 5 hours remaining to
deal with his final challenge: an activated nuclear bomb protected by a security code. Can you help
him figure out the code and deactivate it? Events occur in real time.
The government hackers at CTU (Counter-Terrorist Unit) have learned some things about the code,
but they still haven’t quite solved it. They know it’s a single, strictly positive, integer. They also know
several clues of the form “when divided by X, the remainder is one of {Y1, Y2, Y3, . . . , Yk}”. There are
multiple solutions to these clues, but the code is likely to be one of the smallest ones. So they’d like
you to print out the first few solutions, in increasing order.
The world is counting on you!
Input
Input consists of several test cases. Each test case starts with a line containing C, the number of clues
(1 ≤ C ≤ 9), and S, the number of desired solutions (1 ≤ S ≤ 10). The next C lines each start
with two integers X (2 ≤ X) and k (1 ≤ k ≤ 100), followed by the k distinct integers Y1, Y2, . . . , Yk
(0 ≤ Y1, Y2, . . . , Yk < X).
You may assume that the X’s in each test case are pairwise relatively prime (ie, they have no
common factor except 1). Also, the product of the X’s will fit into a 32-bit integer.
The last test case is followed by a line containing two zeros.
Output
For each test case, output S lines containing the S smallest positive solutions to the clues, in increasing
order.
Print a blank line after the output for each test case.
Sample Input
3 2
2 1 1
5 2 0 3
3 2 1 2
0 0
Sample Output
5
13
题目大意
求一个数N,给出C和S,表示有C个条件,每个条件有X 和 k,然后是该个条件的k个yi,即NmodX=yj,输出满足的最小的S个N,要求正整数。

解题思路
total为所有的k的乘积,也就是可以作为一组完整限定条件的可能数,当个确定条件可以用中国剩余定理处理。但是如果total太大的话,处理的情况比较多。不过total数大的时候,可以通过枚举N来判断,找到一组k/x最小的最为枚举基准,然后判断即可

#include <cstdio>
#include <cstring>
#include <set>
#include <vector>
#include <algorithm>
 
using namespace std;
typedef long long ll;
const int maxc = 15;
const int maxk = 105;
const int limit = 10000;
 
ll total;
int C, S, X[maxc], k[maxc];
int bestc, Y[maxc][maxk];
set<int> vis[maxc];
 
void init () {
    total = 1;
    bestc = 0;
    for (int i = 0; i < C; i++) {
        scanf("%d%d", &X[i], &k[i]);
        total *= k[i];
 
        for (int j = 0; j < k[i]; j++)
            scanf("%d", &Y[i][j]);
        sort(Y[i], Y[i] + k[i]);
 
        if (k[i] * X[bestc] < k[bestc] * X[i])
            bestc = i;
    }
}
 
void solveEnum (int s) {
 
    for (int i = 0; i < C; i++) {
        if (i == s)
            continue;
 
        vis[i].clear();
        for (int j = 0; j < k[i]; j++)
            vis[i].insert(Y[i][j]);
    }
 
    for (int t = 0; S; t++) {
 
        for (int i = 0; i < k[s]; i++) {
            ll n = (ll)X[s] * t + Y[s][i];
 
            if (n == 0)
                continue;
 
            bool flag = true;
            for (int c = 0; c < C; c++) {
                if (c == s)
                    continue;
 
                if (!vis[c].count(n%X[c])) {
                    flag = false;
                    break;
                }
            }
 
            if (flag) {
                printf("%lld\n", n);
                if (--S == 0)
                    break;
            }
        }
    }
}
 
int a[maxc];
vector<int> sol;
 
void gcd(ll a, ll b, ll& d, ll& x,ll& y) {
    if (!b) {
        d = a;
        x = 1;
        y = 0;
    } else {
        gcd(b, a%b, d, y, x);
        y -= x*(a/b);
    }
}
 
int china (int n, int* s, int* m) {
    ll M = 1, d, y, x = 0;
 
    for (int i = 0; i < n; i++)
        M *= m[i];
 
    for (int i = 0; i < n; i++) {
        ll w = M / m[i];
        gcd(m[i], w, d, d, y);
        x = (x + y * w * a[i]) % M;
    }
    return (x+M)%M;
}
 
void dfs (int dep) { 
    if (dep == C)
        sol.push_back(china(C, a, X));
    else {
        for (int i = 0; i < k[dep]; i++) {
            a[dep] = Y[dep][i];
            dfs(dep+1);
        }
    }
}
 
void solveChina () {
    sol.clear();
    dfs(0);
    sort(sol.begin(), sol.end());
 
    ll M = 1;
    for (int i = 0; i < C; i++)
        M *= X[i];
 
    for (int i = 0; S; i++) {
        for (int j = 0; j < sol.size(); j++) {
            ll n = M * i + sol[j];
            if (n > 0){
                printf("%lld\n", n);
                if (--S == 0)
                    break;
            }
        }
    }
}

中国剩余定理(孙子定理)详解
问题:今有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?

简单点说就是,存在一个数x,除以3余2,除以5余三,除以7余二,然后求这个数。上面给出了解法。再明白这个解法的原理之前,需要先知道一下两个定理。

定理1:两个数相加,如果存在一个加数,不能被整数a整除,那么它们的和,就不能被整数a整除。

定理2:两数不能整除,若除数扩大(或缩小)了几倍,而被除数不变,则其商和余数也同时扩大(或缩小)相同的倍数(余数必小于除数)。

以上两个定理随便个例子即可证明!

现给出求解该问题的具体步骤:

1、求出最小公倍数

lcm=357=105

2、求各个数所对应的基础数

(1)105÷3=35

35÷3=11…2 //基础数35

(2)105÷5=21

21÷5=4…1

定理2把1扩大3倍得到3,那么被除数也扩大3倍,得到21*3=63//基础数63

3、105÷7=15

15÷7=2…1

定理2把1扩大2倍得到2,那么被除数也扩大2倍,得到15*2=30//基础数30

把得到的基础数加和(注意:基础数不一定就是正数)

35+63+30=128

4、减去最小公倍数lcm(在比最小公倍数大的情况下)

x=128-105=23

那么满足题意得最小的数就是23了。一共有四个步骤。下面详细解释每一步的原因。

(1)最小公倍数就不解释了,跳过(记住,这里讨论的都是两两互质的情况)

(2)观察求每个数对应的基础数时候的步骤,比如第一个。105÷3=35。显然这个35是除了当前这个数不能整除以外都能够被其他数整除,就是其他数的最小公倍数。相当于找到了最小的起始值,用它去除以3发现正好余2。那么这个基础数就是35。记住35的特征,可以整除其他数但是不能被3整除,并且余数是2。体现的还不够明显,再看下5对应的基础数。21是其他数的最小公倍数,但是不能被5整除,用21除以5得到的余数是1,而要求的数除以5应该是余1的。所以余数被扩大,就得到了相应的基础数63。记住这个数的特征,可以被其他数整除但是被5除应该余三。同理,我们得到了第三个基础数23,那么他的特征就是:可以被其他数整除,但是不能被7整除,并且余数为2。

(3)第三步基础数加和,为什么要这样做呢?利用就是上面提到的定理1。

35+63+30=128。对于3来说,可以把63+30的和看作一个整体,应该他们都可以被3整除。看着上面写出的三个数的特征,运用定理1来说,就是在35的基础上加上一个可以被3整除的倍数,那么得到的结果依然还是满足原先的性质的,就是128除以同样还是余2的。同理,对于5还说,这个数被除之后会剩余3;对于7来说,被除之后剩余2。所以说,我们当前得到的这个数是满足题目要求的一个数。但是这个数是不是最小的,那就不一定了。

(4)应该不能确定是不是最小的数,这个时候就要用到他们的最小公倍数了。最小公倍数顾名思义,一定是一个同时被几个数整除的最小的一个数,所以减去它剩余下来的余数还是符合题意要求的。当然也同样可以运用定理1来解释,只不过是加法变成了减法,道理还是一样的。当然具体要不要剪还是要看和lcm的大小关系的。

稍微的总结一下:就是已知m1,m2,m3是两两互质的正整数,求最小的正整数x,使它被m1,m2,m3除所得的余数分别是c1,c2,c3。孙子定理的思想便是线分别求出被其中数mi整除余1而被另外两个数整除的数Mi(i=1,2,3),则所求数之一的便是c1M1+c2M2+c3M3。由此我们可以得到n个两两互质数的情况。证明上面已经一步一步给出。

那么,到此为止基本的中国剩余定理的内容我们以及了解了,包括解答方法。那么如何编码呢?按照上面这个思路去编码,其实并不难。一共分为四大步。但是,大多数人的困惑在于如何求取基础数。这里呢,提供两种方法:

(1)第一种就是一直递增,直到找到。例如:3的基础数,35是其他数的最小公倍数。那么就从35开始,一直自增,直到余数为2,便停止(利用while循环)。

(2)第二种方法呢就是辗转相除法上得来的。这里的例子体现的不够明显,应当看看去求取乘法逆元的过程,下面讲的内容和乘法逆元有很大的关系,所以还是看看的好。简单举个例子:

假设现在三个数分别是14,3,5,它们两两互质,且要求的数除以5余3。求5对应的基础数。有:

42÷5=8…2

5÷2=2…1

所以1=5-22=5-2(42-85)=-242+17*5

那么-242=-84 175=85 -84+85=1

把1扩大3倍变成3,则有-84*3=-252也就是5对应的基础数。

第一点: 基础数可以是负数,这个之前点到过。//并且下面的解法就是有这样的。

第二点: 当得到余数为1的时候后面的算式相当于是一个回溯的过程,最后解到-2*42。 但是还只不过是余数是1的情况对应的数,再运用定理2我们就得到了-252这个基础数。实际上要是看过乘法逆元,这里实际就是乘法逆元的求解过程,而-2也就是42关于15取模的乘法逆元。

模板

#include<cstdio>
#define ll long long
//扩展欧几里得算法 
void gcd(ll a,ll b,ll &d,ll &x,ll &y)
{
    if(b==0){
        d=a;
        x=1,y=0;
    }
    else{//else不能省略 
        gcd(b,a%b,d,y,x);
        y-=(a/b)*x;
    }
}
//中国剩余定理 
ll China(int n,ll *m,ll *a)
{
    ll M=1,d,y,x=0;
    for(int i=0;i<n;i++) M*=m[i];
    for(int i=0;i<n;i++){
        ll w=M/m[i];
        gcd(m[i],w,d,d,y);
        x=(x+y*w*a[i])%M;
    }
    return (x+M)%M;
}
ll m[15],a[15];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=0;i<n;i++)
        scanf("%lld%lld",&m[i],&a[i]);
    printf("%lld",China(n,m,a));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值