1. 程式人生 > >UVA-11754-Code Feat-數論-中國剩餘定理+列舉.md

UVA-11754-Code Feat-數論-中國剩餘定理+列舉.md

【Description】

Hooray! Agent Bauer has shot the terrorists, blown up the bad guy base, saved the 
hostages, exposedthe moles in the government, prevented an environmental catastrophe,
and found homes for threeorphaned kittens, all in the span of 19 consecutive hours. But
now, he only has 5 hours remaining todeal with his  nal challenge:
an activated nuclear bomb protected by a security code. Can you helphim gure out the code and deactivate it? Events occur in real time. he 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 offY1;Y2;Y3;..;Yk"There aremultiple solutions to these clues,but the code is likely to be one of the smallest ones. So they'd likeyou to print out the rst 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 Xs in each test case are pairwise relatively prime (ie, they 
have no common factor except 1).  Also, the product of the Xs 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.

【Examples】

Sample Input

3 2
2 1 1
5 2 0 3
3 2 1 2
0 0

Sample Output

5
13

【Problem Description】

有C個互質的數,對於每個數,分別有k個餘數,求最小的S個數,滿足這S個數對Ci取模的餘數等於k個餘數中的其中一
個

【Solution】

第一想法就是列舉所有餘數組合,分別進行中國剩餘定理求解出對應的數,最後輸出最小的S個數即可。
事實證明超時,原因是當餘數個數過大時,列舉會很慢,不如直接暴力來的快。
所以將其分為兩部分,當所有餘數個數的乘積小於10000時,用中國剩餘定理求解,否則,直接暴力求解。

中國剩餘定理:
基礎定理:
定理1:幾個數相加,如果存在一個加數,不能被整數a整除,那麼它們的和,就不能被整數a整除。
定理2:兩數不能整除,若除數擴大(或縮小)了幾倍,而被除數不變,則其商和餘數也同時擴大(或\\縮小)相同的倍數
(餘數必小於除數)。

例:求一個最小的數,他分別除以357,餘數分別為232。
找出所有除數的最小公倍數lcm(357=105。
對於3,lcm/3=35,35%3=2,此時找到基礎數35,它滿足能整除57,且除以三32.
對於5,lcm/5=21,21%5=1,由定理2可得,3*21%5=3,此時找到基礎數63,他能被37整除,且除以53.
對於7,lcm/7=15,15%7=1,同理得,2*15%7=2,此時找到基礎數30,他能被35整除,且除以72.
最後由定理1將所有找的的數加起來35+63+30=128,因為是求最小的數,所以在對他們的最小公倍數取模即23.

編碼用擴充套件歐幾里得來做,原理如下:
對於a[i],lcm/a[i]=m, 假設m*x%a[i]==1,則m*x=a[i]*y+1,則m*x+(-a[i]*y)=1,即我們用擴充套件歐幾里得
求出x即可,那麼基礎數就等於m*x*r[i](其中a[i],為除數,r[i]為餘數)

【Code】

/*
 * @Author: Simon 
 * @Date: 2018-09-12 21:35:19 
 * @Last Modified by: Simon
 * @Last Modified time: 2018-09-23 20:28:22
 */
#include <bits/stdc++.h>
using namespace std;
typedef int Int;
#define int long long
#define INF 0x3f3f3f3f
#define maxn 100005
map<int, int> p;
int a[maxn],r[maxn];//除數,餘數的一個組合
set<int> g[maxn],ans;//g[i][j],為所有a[i]的餘數
set<int> s[maxn];
int exgcd(int a,int b,int &x,int &y)//擴充套件歐幾里得
{
    if(b==0)
    {
        x=1,y=0;
        return a;
    }
    int ans=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return ans;
}
int China(int C)//中國剩餘定理
{
    int M=1,x,y,ans=0;
    for(int i=1;i<=C;i++) M*=a[i];//最小公倍數
    for(int i=1;i<=C;i++)
    {
        int w=M/a[i];
        int d=exgcd(a[i],w,x,y);
        ans=(ans+y*w*r[i])%M;//所有基礎數的和
    }
    return (ans+M)%M;//答案
}
void dfs(int c,int C)//dfs列舉所有餘數的組合
{
    if(c==C+1)
    {
        ans.insert(China(C));
        return ;
    }
    for(auto v:g[c])
    {
        r[c]=v;
        dfs(c+1,C);
    }
}
void solve_enum(int t,int C,int S)//>10000時,直接暴力
{
    for(int i=0;S!=0;i++)
    {
        for (auto v : g[t])
        {
            int n = i * a[t] + v;
            if(!n) continue;
            int flag=0;
            for(int j=1;j<=C;j++)
            {
                if(j==t) continue;
                if(!g[j].count(n%a[j])){flag=1;break;}
            }
            if(!flag) {cout<<n<<endl;if(--S==0) return ;}
        }
    }
}
Int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int C, S;
    while (cin >> C >> S && C && S)
    {
        ans.clear();int t=1,tol=1;p.clear();int M=1;
        for (int i = 1; i <= C; i++)
        {
            cin >> a[i];M*=a[i];
            int n;
            cin >> n;
            for (int j = 1; j <= n; j++)
            {
                int x;
                cin >> x;
                g[i].insert(x);
            }
            tol*=g[i].size();//所有餘數個數的乘積
            if(g[i].size()*a[t]<g[t].size()*a[i]) t=i;//找到最小的i,滿足num[i]/a[i]最小,其中num[i]為餘數的個數,a[i]為除數
        }
        if(tol<10000)
        {
            dfs(1, C);
            for(int i=0;S!=0;i++)//按順序輸出最小的數,且要保證不為0
            {
                for(auto v:ans)
                {
                    int n=i*M+v;
                    if(n>0)
                    {
                        cout<<n<<endl;
                        if(--S==0) break;
                    }
                }
            }
        }
        else solve_enum(t,C,S);
        for (int i = 0; i <= C; i++)
            g[i].clear();
        cout<<endl;//題目要求輸出一個空行
    }
    cin.get(), cin.get();
    return 0;
}