有一个正整数N 满足C 个条件,每个条件都形如"它除以X 的余数在集合{Yt. Y2, ..., Yk}中",所有条件中的X 两两互素,你的任务是找出最小的S 个解。
【分析】
"除以X 的余数在集合{Y1 ,Y2. . ., Yk} 中"这个条件很不好处理。如果我们知道这个余数具体是中的哪一个,问题就会简单很多。一种容易想到的方法是枚举每个集
中取哪个元素,它可以解决样例。样例有3 个条件,即x mod 2=1, x mod 5=0 或3 , x mod 3=1或2 ,一共有如下4 种可能.
当所有k 的乘积很大时这种方法会很慢,此时我们有另外一个方法,直接枚举x。找一个k/X 最小的条件(比如样例中的第二个条件k=2 , X=5) , 按照t = 0, 1 , 2 , .. . 的顺序枚举所有的tX+Yj (相同的t 按照从小到大的顺序枚举Y;) , 看看是否满足条件。因为所有k 的乘积很大,这个算法很快就能找到解。有两个需要注意的地方。
首先,如果用中国剩余定理求解,若得到的解不超过S 个,需要把这些解加上M, 2M, 3M, ...,直到解足够多(M 为所有X 的乘积)。其次,根据题意,。不能算作解,因为它不是正整数。但不要因此把M, 2M, 3M, ...这些解也忽略了。代码如下。
#include <cstdio>
#include <algorithm>
#include <set>
#include <vector>
using namespace std;
typedef long long LL;
const int MAXC = 10, K = 100 + 1, LIMIT = 10000;
int x[MAXC], k[MAXC], y[MAXC][K], C;
set<int> values[MAXC];
void sloveEnum(int s,int bc){
for(int i = 0; i < C; i++){
if(i != bc){
values[i].clear();
for(int j = 0; j < k[i]; j++)
values[i].insert(y[i][j]);
}
}
for(LL t = 0; s; t++){
for(int i = 0; i < k[bc]; i++){
LL n = (LL)t*x[bc] + y[bc][i];
if(n == 0) continue; // 只输出正数解
bool ok = true;
for(int j = 0; j < C; j++){
if(j != bc && !values[j].count(n % x[j])){
ok = false;
break;
}
}
if(ok){
printf("%lld\n",n);
if( --s == 0) return;
}
}
}
}
//求整数x和y,使得ax+by=d. 且|x+y|最小。其中d=gcd(a,b)
// 即使a, b在int范围内,x和y有可能超出int范围
void extendGcd(LL a,LL b,LL &d,LL &x,LL &y){
if(!b){
x = 1;
y = 0;
d = a;
return;
}
extendGcd(b, a%b, d, y, x);
y -= a/b*x;
}
// n个方程:x=a[i](mod m[i]) (0<=i<n);中国剩余定理
LL chinaRemainder(int n,int *a,int *m){
LL M = 1, d, y, ans = 0;
for(int i = 0; i < C; i++) M *= m[i];
for(int i = 0; i < n; i++){
LL w = M / m[i];
extendGcd(m[i], w, d, d, y);
ans = (ans + y*w*a[i]) % M;
}
return (ans + M) % M;
}
vector<LL> ans;
int a[MAXC]; //记录所有组合的可能。
void dfs(int cur){
if(cur == C){
ans.push_back(chinaRemainder(C, a, x));
return;
}
for(int i = 0; i < k[cur]; i++){
a[cur] = y[cur][i];
dfs(cur+1);
}
}
void solveChina(int s){
ans.clear();
dfs(0);
sort(ans.begin(), ans.end());
LL M = 1;
for(int i = 0; i < C; i++)
M *= x[i];
for(int t = 0; s; t++){
for(int i = 0; i < ans.size(); i++){
LL n = (LL)t*M + ans[i];
if( n <= 0) continue;
printf("%lld\n", n);
if(--s == 0) return;
}
}
}
int main(int argc, char** argv) {
int s;
while( ~scanf("%d%d", &C, &s) && C){
int total = 1, bestc = 0;
for(int i = 0; i < C; i++){
scanf("%d%d",&x[i], &k[i]);
total *= k[i]; //k种余数
for(int j = 0; j < k[i]; j++)
scanf("%d",&y[i][j]);
sort(y[i], y[i]+k[i]);
if(k[i] * x[bestc] < k[bestc] * x[i]) bestc = i; // k[c]/X[c] < k[bestc]/X[bestc]
}
if(total > LIMIT) sloveEnum(s, bestc); // 能够组合的数大于问题要求LIMIT
else solveChina(s);
printf("\n");
}
return 0;
}