Code Feat UVA - 11754 【中国剩余定理+枚举】

题目大意:

有一个正整数N满足C个条件,每个条件形如:“N除以X的余数在集合 Y 1 、 Y 2 、 ⋅ ⋅ ⋅ 、 Y k {Y_1、Y_2、\cdot\cdot\cdot、Y_k} Y1Y2Yk

求最小的S个满足题目要求的解

数据约束:C<10,S<=10,给出的C个X是互质的且乘积在int范围内,给出的C个k都满足k<=100,

分析:

第一反应是中国剩余定理:

C C C个形如 x ≡ a i   ( m o d   m i ) x≡a_i\ (mod \ m_i) xai (mod mi)的方程组成的同余方程组,其中一个特解如下:

a n s = ∑ i = 1 C a i ⋅ M i ⋅ t i ans= \sum_{i=1}^Cai\cdot M_i\cdot t_i ans=i=1CaiMiti

令 m = m 1 ⋅ m 2 ⋅ ⋅ ⋅ m C , 则 M i = m / m i , t i 为 M i 模 m i 意 义 下 的 逆 元 令m=m_1\cdot m_2\cdot\cdot\cdot m_C ,则M_i=m/m_i ,t_i为M_i模m_i意义下的逆元 m=m1m2mCMi=m/mi,tiMimi

对于这道题, a i a_i ai的值不固定,即第i个方程的 a i a_i ai值有 k i k_i ki种可能,所以有了下面这种做法:

①利用深搜穷尽 a i a_i ai的所有组合情况,然后中国剩余定理求解,取较小的S个解即可(若解不满S个,则加上m)

这种做法的复杂度取决于状态的个数(深搜),即C个 k i k_i ki的乘积, 10 0 9 100^9 1009,显然过于庞大了

再看另一种暴力做法:

②选择某一个模方程 x ≡ a i   ( m o d   m i ) x≡a_i\ (mod \ m_i) xai (mod mi),该方程的解为 a i + t ∗ m i   , t = 0 、 1 、 2 、 3 ⋅ ⋅ ⋅ a_i+t*m_i \, ,t=0、1、2、3\cdot\cdot\cdot ai+tmi,t=0123,然后令t从0开始不断增长,并检验此时该方程的解是否也同时是其他几个方程的解,若是则我们找到了一个解

当然,在这道题中对于某一个方程,它的 a i a_i ai值是不固定的,所以我们外层循环t,内层循环k个 a i a_i ai值即可(将这k个值从小到大排列)

这种算法的复杂度没法分析,和解在数轴上的分布状况是有关的。可以肯定的是,当k的乘积很大时,解的分布是比较稠密的:

例: x ≡ a i   ( m o d   10 ) x≡a_i\ (mod \ 10) xai (mod 10),若k越大(即 a i a_i ai取值的可能性越多),该方程的解的个数越多

那么n个方程的解的分布情况应该和 k 1 ⋅ k 2 ⋅ ⋅ ⋅ k C m 1 ⋅ m 2 ⋅ ⋅ ⋅ m C \frac{k_1\cdot k_2\cdot\cdot\cdot k_C}{m_1\cdot m_2\cdot \cdot\cdot m_C} m1m2mCk1k2kC 呈正相关,而分母是int范围内的数,所以大致上当k的乘积超过10000后,就可以不超过 1 0 6 10^6 106级别的运算时间找到10个解(个人估计)

那么还有一个问题就是具体选择哪个方程呢?答案是k越小越好(减少内层循环次数),m越大越好(在数轴上一次跳过的数字更多)

那么不妨取k/x值最小的方程

END

————————————————————————

具体实现可看代码

代码:

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

int C,S;
long long  x[15],k[15];
long long  y[15][105];
long long  M[15],t[15],a[15];
long long  m,best; //m表示xi的乘积,best表示k/x最小的那一行
vector<long long >ans;
set<long long >vis[15];


long long exgcd(long long a,long long b,long long &x,long long &y){ //扩展欧几里得模板
  if(a%b==0) {x=0;y=1;return b;}
  long long d=exgcd(b,a%b,x,y);
  long long temp=x;
  x=y;y=temp-a/b*y;
  return d;
}

void dfs(int step){ //深搜枚举所有可能
  if(step==C+1){
     long long sum=0;
     for(int i=1;i<=C;i++)
         sum+=a[i]*M[i]*t[i],sum%=m;
     ans.push_back((sum+m)%m);
     return ;
  }
  for(int i=1;i<=k[step];i++){
    a[step]=y[step][i];
    dfs(step+1);
  }
}

void solve1(){
  ans.clear(); 
  for(int i=1;i<=C;i++) M[i]=m/x[i];
  for(int i=1;i<=C;i++){
     long long temp;
     exgcd(M[i],x[i],t[i],temp);
  }
  dfs(1);
  sort(ans.begin(),ans.end()); 
  for(int i=0;S;i++){
    for(int j=0;j<ans.size();j++){
         long long op=ans[j]+i*m;
         if(!op) continue;
         printf("%lld\n",op);
         S--;
         if(!S) break;
    }
  }
}

void solve2(){
   for(int i=1;i<=C;i++)
     if(i!=best){
        vis[i].clear();
        for(int j=1;j<=k[i];j++)
              vis[i].insert(y[i][j]);
   }
   for(int t=0;S;t++){
     for(int i=1;i<=k[best];i++){
         long long ans=y[best][i]+t*x[best];
         if(!ans) continue;
         bool flag=true;
         for(int j=1;j<=C;j++){
             if(j!=best){
                 if(!vis[j].count(ans%x[j]))  {flag=false;break;}
             }
         }
         if(flag) {
            printf("%lld\n",ans);
            S--;
         }
         if(!S) break;
     }
   }
}

int main(){
  while(~scanf("%d%d",&C,&S)&&C&&S){
      int tot=1;
      best=1;
      m=1;
      for(int i=1;i<=C;i++){
         scanf("%lld%lld",&x[i],&k[i]);
         tot*=k[i];
         m*=x[i];
         for(int j=1;j<=k[i];j++)
            scanf("%lld",&y[i][j]);
         sort(y[i]+1,y[i]+k[i]+1);
         if(k[i]*x[best]<k[best]*x[i]) best=i;
      }
      if(tot>10000) solve2();
      else solve1();
      printf("\n");
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值