有一個正整數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;
}