UVA - 11754 Code Feat

传送门:点击打开链接

题意:给定几组除数和余数,被除数两两互质,每个被除数对应的余数有多个,问满足题意的被除数,并从小到大排列

思路:中国剩余定理+暴力 中国剩余定理在解决余数较多问题时显得较为笨拙,于是,当余数达到某个值以上时,暴力求解更快一些

反思:这道题我T了好久,最后发现在暴力求解时,本可以用set存储每个除数所对应的余数,然后判断某个数模该除数的结果是否在set中,然而我却用dfs找......(现在想来真是智障),以前从没用过set,以后多用吧...红黑树还是快的啊

收获:在这道题上有一个难懂的地方,本题有signmaxi标记,这个标记对应的除数是用来在暴力时建立被除数的,选择标记的原则是m[i]/k[i]值最大(m[i]:除数,k[i]:除数对应的余数个数),这么选的原因是,暴力时建立被除数个数的多少取决于除数大和余数少的数对(以5和1e6举例, 5m+n(m从1开始循环,0<=n < 5)循环许多次也是小于1e6,这样轻率的选择标记徒增时间复杂度 | 余数越少,5m + n的n的选择性就越少,能满足该等式的整数就越少。仔细想想当n只有一种取值时1000000m+n在(0, LLONG_MAX)内还是很稀疏的)

代码如下:

#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
vector<ll> ans;
set<ll> vis[15];
ll m[15], r[15][110], mm[15], k[15], all, tmp[15];
int c, s, signmaxi;
ll china()
{
    ll sum = 0LL;
    for(int i = 0; i < c; i++)
    {
        sum = (sum%all + (tmp[i]%all)*(mm[i]%all)%all)%all;
    }
    return sum > 0 ? sum : sum + all;
}
void chinadfs(int ci)
{
    if(ci == c)
    {
        ll res = china();
        ans.push_back(res);
        return;
    }
    for(int i = 0; i < k[ci]; i++)
    {
        tmp[ci] = r[ci][i];
        chinadfs(ci + 1);
    }
}
void exgcd(ll &x, ll &y, ll a, ll b)
{
    if(b == 0)
    {
        x = 1;
        y = 0;
        return;
    }
    exgcd(x, y, b, a % b);
    ll tmp = x;
    x = y;
    y = tmp - a / b * y;
}
void chinasolve()
{
    for(int i = 0; i < c; i++)
    {
        mm[i] = all / m[i];
    }
    ll x, y;
    for(int i = 0; i < c; i++)
    {
        exgcd(x, y, mm[i], m[i]);
        mm[i] *= x;
    }
    chinadfs(0);
    sort(ans.begin(), ans.end());
    int siz = ans.size();
    int ss = s;
    for(int i = 0; ss; i++)
    {
        for(int j = 0; j < siz; j++)
        {
            printf("%lld\n", ans[j] + i * all);
            ss--;
            if(ss == 0)
                break;
        }
    }
}
void violent()
{
    for(int i = 0; i < c; i++)
    {
        if(i == signmaxi)
            continue;
        for(int j = 0; j < k[i]; j++)
            vis[i].insert(r[i][j]);
    }
    int ss = s;
    bool ju = true;
    for(int j = 0; ss; j++)
    {
        for(int i = 0; i < k[signmaxi]; i++)
        {
            ju = true;
            ll tmp = r[signmaxi][i] + j * m[signmaxi];
            if(tmp == 0)
                continue;
            for(int a = 0; a < c; a++)
            {
                if(a == signmaxi) continue;
                if(!vis[a].count(tmp % m[a]))
                {
                    ju = false;
                    break;
                }
            }
            if(ju)
            {
                printf("%lld\n", tmp);
                ss--;
                if(ss == 0)
                    break;
            }
        }
    }
}
int main()
{
    while(~scanf("%d%d", &c, &s) && (c || s))
    {
        for(int i = 0; i < 15; i++)
            vis[i].clear();
        all = 1LL;
        ans.clear();
        signmaxi = 0;
        ll total = 1LL;
        for(int i = 0; i < c; i++)
        {
            scanf("%lld%lld", &m[i], &k[i]);
            if(k[i] * m[signmaxi] < k[signmaxi] * m[i])
            {
                signmaxi = i;
            }
            all *= m[i];
            total *= k[i];
            for(int j = 0; j < k[i]; j++)
            {
                scanf("%lld", &r[i][j]);
            }
            sort(r[i], r[i] + k[i]);
        }
        if(total > 1000LL)
            violent();
        else
            chinasolve();
        printf("\n");
    }
}


从Time limit exceed到0秒瞬过,还是有些小激动的...

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值