题目大意:
有一个正整数N满足C个条件,每个条件形如:“N除以X的余数在集合 Y 1 、 Y 2 、 ⋅ ⋅ ⋅ 、 Y k {Y_1、Y_2、\cdot\cdot\cdot、Y_k} Y1、Y2、⋅⋅⋅、Yk”
求最小的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) x≡ai (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=1Cai⋅Mi⋅ti
( 令 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=m1⋅m2⋅⋅⋅mC,则Mi=m/mi,ti为Mi模mi意义下的逆元)
对于这道题, 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) x≡ai (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+t∗mi,t=0、1、2、3⋅⋅⋅,然后令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) x≡ai (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} m1⋅m2⋅⋅⋅mCk1⋅k2⋅⋅⋅kC 呈正相关,而分母是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;
}