Code Feat UVA - 11754 中國剩餘定理

題目鏈接

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章