中国剩余定理,枚举(数论难题,UVA 11754)

就是给你C个同余方程组,每个方程组有Kc个模方程。一个同余方程组的解是其中每个模方程的解的并集,所有同余方程组的解是单个同余方程组的解的交集。求最小的S个解。


无非就是枚举方程的组合,然后跑中国剩余定理或者从小到大枚举x判断是否可行。但是当时觉得都很慢。没有发现互补的一面。


然后就想用一些数论的方法把一个方程组变成单个方程,改编一下china函数的算法,然后发现没办法做到,然后卒。


事实上枚举x是有技巧的,不是全部枚举一遍,再全部判断一遍。你可以把一些本来用于判断的条件用来当做枚举的限制条件,再用剩下的条件来判断是否可行。对半分为最好。但出于编程方便,一般选一个限制做多的条件就足够了。在这题里,选k/x最小的那个,那么k相对小,x相对大。这样枚举答案tx+yi时,tx增长很快,yi也不用枚举很多。因此,枚举量相对较小。


至于枚举方程的组合,当数据小的时候速度还不错,但数据一大就爆炸。


大白书上说:

if(tot>10000) solve_enum(p);
else solve_china();


数据大时铁定不能用solve_china();只好用solve_enum(p);啦。
但最难理解的就是为什么数据小时不能用solve_enum(p);而只能用solve_china();


答案就是这两种方法的目标与时间复杂度是不同的。

solve_enum(p);只找最小的S个解,是多项式的时间复杂度。

而solve_china();是找所有可行解,是指数型的时间复杂度。

两者的增长速度是不一样的。

当数据量很大时,solve_china();的时间复杂度将远远超过solve_enum(p);因为可行解太多了,而我们又不需要那么多的可行解,我们只要最小的S个就好了不是吗?这就造成了极大地浪费。

而在数据量很小的时候,solve_enum(p);毕竟是枚举,要迭代很多次的,而且大部分的枚举都是错误的尝试。而此时solve_china();只找可行解,而可行解很少。所以solve_china();会快很多。


然后就是关于枚举方程的组合,可以用dfs,思路清晰,代码实现也方便。

用位向量的话有多少位也不确定。或者搞一大堆循环来枚举也不知道要多少层。还是dfs最好。


然后枚举Y要从小到大,因为Y<X所以先枚举t,然后再从小到大枚举y,保证x递增。


然后用solve_china();找到的解都是剩余系里的,先排个序,再输出。

如果不够用,就要加上M再输出一遍,直到够为止,显然输出的结果都是递增的。

还有就是0不能是解。


代码

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

ll C,S;
ll X[10],K[10];
ll Y[10][110];

void ex_gcd(ll a,ll b,ll& d,ll& x,ll& y)
{
    if(!b){d=a;x=1;y=0;}
    else {ex_gcd(b,a%b,d,y,x);y-=x*(a/b);}
}

ll china(ll n,ll* a,ll* m)
{
    ll M=1,d,x=0,y;
    for(ll i=0;i<n;i++) M*=m[i];
    for(ll i=0;i<n;i++)
    {
        ll w=M/m[i];
        ex_gcd(m[i],w,d,d,y);
        x=(x+y*w*a[i])%M;
    }
    return (x+M)%M;
}

void solve_enum(ll p)
{
    set<ll>s[10];
    for(ll c=0;c<C;c++) if(c!=p)
        for(ll i=0;i<K[c];i++)
            s[c].insert(Y[c][i]);
    for(ll t=0;S;t++)
        for(ll i=0;i<K[p];i++)
        {
            ll ans=t*X[p]+Y[p][i];
            if(!ans) continue;
            bool ok=true;
            for(ll c=0;c<C;c++) if(c!=p)
                if(!s[c].count(ans%X[c]))
                {
                    ok=false;
                    break;
                }
            if(ok)
            {
                printf("%lld\n",ans);
                if(--S==0) break;
            }
        }
}

vector<ll>vec;
ll a[10];

void dfs(ll d)
{
    if(d==C) vec.push_back(china(d,a,X));
    else for(ll i=0;i<K[d];i++)
    {
        a[d]=Y[d][i];
        dfs(d+1);
    }
}

void solve_china()
{
    vec.clear();
    dfs(0);
    sort(vec.begin(),vec.end());
    ll M=1;
    for(ll i=0;i<C;i++)
        M*=X[i];
    for(ll i=0;S;i++)
        for(unsigned int j=0;j<vec.size();j++)
        {
            ll ans=vec[j]+i*M;
            if(!ans) continue;
            printf("%lld\n",ans);
            if(--S==0) break;
        }
}

int main()
{
    while(scanf("%lld %lld",&C,&S)==2&&C&&S)
    {
        ll tot=1;
        ll p=0;
        for(ll c=0;c<C;c++)
        {
            scanf("%lld %lld",&X[c],&K[c]);
            tot*=K[c];
            if(K[p]*X[c]>K[c]*X[p])
                p=c;
            for(ll i=0;i<K[c];i++)
                scanf("%lld",&Y[c][i]);
            sort(Y[c],Y[c]+K[c]);
        }
        if(tot>10000) solve_enum(p);
        else solve_china();
        puts("");
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值