Code Feat UVA - 11754(中国剩余定理+枚举)

Code Feat UVA - 11754

题目链接

题目大意:

求一个数N,给出C和S,表示有C个条件,每个条件有X 和 k,然后是该个条件的k个yi,其中存在一个 y j 使得 N y i   ( m o d   X ) ,输出满足的最小的S个N,要求正整数。

题目分析:

根据题目我们知道,C个条件每个条件中到每个数y都有可能是N的余数,因此这里就会有很多的组合,总共到组合种类数是 k i ,所以这个种类数有可能会很大
1. 当种类数比较小的时候我们可以,通过深搜来枚举每种余数的组合,然后每次枚举出一组余数后,通过中国剩余定理求出符合所有条件到同余数,存到vector中,然后还原出被除数即可(枚举商i,N = i × X + y)
2. 当种类数比较大的时候,如果我们继续使用深搜来枚举每种组合情况是不现实的,时间复杂度将会非常大,这个时候我们采用枚举到方式。大体思路是,我们从C个条件中预先选择出最优到一个条件,根据这个最优条件,枚举商i生成N,N = i × X b e s t + y b e s t , j ,就是商×除数+余数=被除数,然后取模其他除数X得到r,看对应余数集合中能否有取模后到余数r。那么我们的主要问题就是 什么条件是最优条件? 怎么判断取模后的余数在对应余数集合里?

最优条件:
首先我们知道,一个除法运算中,余数一定要比除数小才行,所以,如果我们选择的那个条件中除数小,对于其他条件除数比它更大的,我们就可能需要枚举更多的商(倍数)才能符合所有条件,举个例子,假设除数5和1e6,如果选择除数为5的条件,N = 5i + y 1 ,选除数为1e6的条件有N = 1e6i + y 2 ,我们可以发现,如果我们选择了前者作为最优条件那么很大范围内很难满足mod 1e6后的余数在后者条件到余数集合中,因为N至少得够大才行,N如果太小在怎么mod 1e6 余数都小于后者条件中最小到余数,这样就会枚举更多次数,所以第一个原则,选择X最大的。
其次因为我们选定最优条件,不但要枚举商,还要枚举里面到余数(其实是二重for循环),所有我们到第二个原则是余数个数k是最小,综合起来就是 X k 最大的那个条件。
判断余数在相应集合中
为了加快速度,我们直接使用set存余数,到时候直接查找就可以了

code:

#include <bits/stdc++.h>
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];//C代表条件数,S代表题目要求到解个数,X[]除数,k[]代表每个条件的可能余数个数
int bestc,Y[maxc][maxk];//bestc代表最佳条件,Y记录每个条件的每个余数是几
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 solve_Enum(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;//存放可能的满足所有条件的余数

ll ex_gcd(ll a,ll b,ll &x,ll &y){
    if(b == 0){
        x = 1;
        y = 0;
        return a;
    }
    ll gcd = ex_gcd(b,a%b,y,x);
    y -= a / b * x;
    return gcd;
}

int CRT(int n,int m[]){//中国剩余定理模板
    ll M = 1,y,R,d;
    ll x = 0;
    for(int i = 0; i < n; i++)
        M *= m[i];
    for(int i = 0; i < n; i++){
        ll w = M / m[i];
        d = ex_gcd(m[i],w,y,R);
        x = (x + R * w * a[i] % M) % M;
    }
    return (x + M) % M;
}

void dfs(int dep){
    if(dep == C)
        sol.push_back(CRT(C,X));
    else{
        for(int i = 0; i < k[dep]; i++){//通过深搜枚举余数的不同组合方式
            a[dep] = Y[dep][i];
            dfs(dep+1);
        }
    }
}

void solve_CRT(){//中国剩余定理方式解决
    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];//枚举出n
            if(n > 0){
                printf("%lld\n",n);
                if(--S == 0) break;
            }

        }
    }
}
int main(){
    while(scanf("%d%d",&C,&S) != EOF){
        if(C == 0 && S == 0) break;
        init();
        if(total > limit)
            solve_Enum(bestc);
        else
            solve_CRT();
        puts("");
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/codeswarrior/article/details/81061685
今日推荐