Code Feat (UVA - 11754,CRT + 玄学暴力)

一.题目链接:

UVA - 11754

二.题目大意:

第一行两个整数 C,S.

接下来 C 行,每行开始有两个整数 X[i],K[i],接下来 K[i] 个整数 Y[i][1 ... K[i]]

表示存在一个正整数 N,使得 N 满足 C 个条件:N % X[i] 为 Y[i][1 ... K[i]] 之中的一个数.

输出最小的前 S 个正整数 N.

三.分析:

很容易想到枚举 Y 的所有组合情况,然后 CRT 暴力求解.

但是当 K[i] 的乘积较大时,这样肯定会收获一发 TLE...

于是这里就有一个玄学暴力解法.

找出使得 K[i] / X[i] 最小的下标 i,然后枚举 N = Y[i][j] + X[i] * t,然后逐一 check.

下面口胡一下这样做为什么可以.

由于 K[i] 的乘积比较大,因此待选数集合较多,当我选择了一个 N 后,check 成功的可能性就较大.

这也是 i 为什么选取使得 K[i] / X[i] 最小的下标的原因,K[i] 小说明当前枚举 Y 的个数少,X[i] 大可以让 N 变大的更快.

贴一个大佬的讲解

还有一个问题就是为什么当 K[i] 乘积较小时不可以直接枚举 N

其实上面已经说过, K[i] 乘积小则待选数集合小,check 成功的可能性小.

例如这组数据

3 10
1007 1 1006
1001 1 991
1003 1 1001

经实测,需要枚举 1e7 规模次 N,无疑会 TLE.

好玄学呀~~

四.代码实现:

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int M = (int)1e1;
const int N = (int)1e2;

int c, s;
int x[M + 5];
int k[M + 5];
int y[M + 5][N + 5];
int a[M + 5];
vector <int> vx;
set <int> st[M + 5];

void init()
{
    for(int i = 1; i <= c; ++i)
    {
        st[i].clear();
        for(int j = 1; j <= k[i]; ++j)
        {
            st[i].emplace(y[i][j]);
        }
    }
}

bool check(ll n)
{
    for(int i = 1; i <= c; ++i)
    {
        if(!st[i].count(n % x[i]))
            return 0;
    }
    return 1;
}

void work1(int idx)
{
    init();
    ll n;
    for(int i = 0; s; ++i)
    {
        for(int j = 1; j <= k[idx]; ++j)
        {
            n = y[idx][j] + 1ll * x[idx] * i;
            if(n == 0)  continue;
            if(!check(n))   continue;
            printf("%lld\n", n);
            if(!--s)    break;
        }
    }
}

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

int crt()
{
    ll res = 0, m = 1, M, t, y;
    for(int i = 1; i <= c; ++i)
        m *= x[i];
    for(int i = 1; i <= c; ++i)
    {
        M = m / x[i];
        exgcd(M, x[i], t, y);
        res = (res + a[i] * M % m * t % m) % m;
    }
    return (res + m) % m;
}

void dfs(int u)
{
    if(u == c + 1)
    {
        vx.push_back(crt());
        return;
    }
    for(int i = 1; i <= k[u]; ++i)
    {
        a[u] = y[u][i];
        dfs(u + 1);
    }
}

void work2()
{
    vx.clear();
    dfs(1);
    sort(vx.begin(), vx.end());
    ll m = 1;
    for(int i = 1; i <= c; ++i)
        m *= x[i];
    for(int i = 0; s; ++i)
    {
        for(auto x: vx)
        {
            ll n = x + m * i;
            if(n == 0)  continue;
            printf("%lld\n", x + m * i);
            if(!--s)    break;
        }
    }
}

int main()
{
    while(~scanf("%d %d", &c, &s) && (c + s))
    {
        int idx = 1;
        ll mul_k = 1;
        for(int i = 1; i <= c; ++i)
        {
            scanf("%d %d", &x[i], &k[i]);
            mul_k *= k[i];
            idx = (k[idx] * x[i] > k[i] * x[idx] ? i : idx);
            for(int j = 1; j <= k[i]; ++j)
                scanf("%d", &y[i][j]);
            sort(y[i] + 1, y[i] + k[i] + 1);
        }
        if(mul_k > (int)1e4)    work1(idx);
        else                    work2();
        puts("");
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值