UVA 11754 Code Feat

题目链接:点我


题意:

给你 C 个约束条件,你需要得出 S 个最小的解. 每一个约束条件可以表现为他的某一个ans ,满足 ans mod X = Yi 的形式,

思路:

中国剩余定理: (维基百科) 这里我们可以在选取了每个条件中的余数后用中国剩余定理解决, 那么问题是我们怎么样选出最小的 S 个. 因为我们在选取余数的时候用的是深搜,但是这样如果数据太大很容易超时,那么我们设定一个限制条件,当余数的数量大于某个值的时候,我们就用其他方法, 这个时候我们可以用 X / K 最小的一组开始枚举 它所能组成的所有的数,然后判断这个数,是否符合其他 的条件,如果符合就输出.
另一个情况就是余数个数比较少的情况,那么此时我们可以暴力枚举所有余数的组合情况,然后用中国剩余定理解出ans.

中国剩余定理:

这里写图片描述

对于上面的式子,我们要解出 x 的值,
令 M = m1 * m2 * m3 * m4 ….. mn ;
然后我们设 Mi = M / mi ;
然后取 ti Mi 的关于 mi 逆元(这里求逆元我们用扩展欧几里得算法),
最后我们得出通解: x = ni=1aitiMi + k * M, k Z <script type="math/tex" id="MathJax-Element-1971">Z</script>,

代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iostream>
#include<vector>
#include<set>
using namespace std;

typedef long long LL;
LL x[20];
int k[20];
int c, s;
int a[105];
vector<int > ans;
int num;
int y[20][105];

void exgcd(LL mi, LL xi, LL &e, LL &d){
    if(!xi){
        e = 1;
        d = 0;
    }else {
        exgcd(xi, mi % xi, d, e);
        d-= mi / xi * e;
    }
}

LL china(){//中国剩余定理
    LL m = 1;
    LL cnm = 0;
    LL e, d;
    for(int i = 1; i <= c; ++i)
        m *= x[i];
    for(int i = 1; i <= c;++i){
        LL mi = m / x[i];
        exgcd(mi, x[i], e, d);//扩展欧几里得求逆元
        cnm = (cnm + e * mi * a[i] ) % m;
    }
    return (cnm + m) % m;
}

void dfs(int cur){
    if(cur == c + 1){
       LL tmp = china();
       ans.push_back(tmp);
       return ;
    }
    for(int i = 1; i <= k[cur];++i){
        a[cur] = y[cur][i];
        dfs(cur+1);
    }
}

void solvechina(){
    LL m = 1;
    ans.clear();
    dfs(1);
    sort(ans.begin(), ans.end());
    for(int i = 1; i <= c;++i) m *= x[i];
    for(int i = 0; s; ++i)
    for(int  j = 0; j < ans.size();++j){
        LL ansk = m * i +ans[j];
        if(ansk > 0){
            printf("%lld\n",ansk);
            --s;
            if(s == 0)
                break;
        }
    }
}

set<int > v[20];
void solvenum(int cur){
    for(int  i = 1; i <= c; ++i)
        if(i != cur){
            v[i].clear();
        for(int j = 1; j <= k[i]; ++j)
            v[i].insert(y[i][j]);
    }for(int i = 0; s; ++i)
    for(int j = 1; j <= k[cur]; ++j){
        LL m = (LL)i * x[cur] +y[cur][j];//从小到大枚举ans,
        if (!m) continue;
        bool flag = true;
        for(int l = 1; l <= c; ++l)
            if(l !=cur && !v[l].count(m % x[l])){//判断ans是否符合所有的情况,
                flag = false;
                break;
        }if(flag){
            printf("%lld\n",m);
            --s;
            if(s == 0)
                break;
        }
    }
}


int main(){
    while(scanf("%d %d", &c, &s) != EOF &&(c||s)){
        LL total = 1;
        int limit = 1;
        for(int i = 1; i <= c; ++i){
            scanf("%lld %d", &x[i], &k[i]);
            total *= k[i];
            if(x[i]*k[limit] > x[limit] * k[i])//选取枚举的标准
                limit = i;
            for(int j = 1; j <= k[i]; ++j)
                scanf("%d", &y[i][j]);
                sort(y[i] + 1, y[i] + 1 +k[i]);
        }
        if(total > 10000)
            solvenum(limit);
        else solvechina();
        printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值