Code Feat UVA - 11754
题目大意:
求一个数N,给出C和S,表示有C个条件,每个条件有X 和 k,然后是该个条件的k个yi,其中存在一个 使得 ,输出满足的最小的S个N,要求正整数。
题目分析:
根据题目我们知道,C个条件每个条件中到每个数y都有可能是N的余数,因此这里就会有很多的组合,总共到组合种类数是
,所以这个种类数有可能会很大
1. 当种类数比较小的时候我们可以,通过深搜来枚举每种余数的组合,然后每次枚举出一组余数后,通过中国剩余定理求出符合所有条件到同余数,存到vector中,然后还原出被除数即可(枚举商i,N = i × X + y)
2. 当种类数比较大的时候,如果我们继续使用深搜来枚举每种组合情况是不现实的,时间复杂度将会非常大,这个时候我们采用枚举到方式。大体思路是,我们从C个条件中预先选择出最优到一个条件,根据这个最优条件,枚举商i生成N,N = i ×
+
,就是商×除数+余数=被除数,然后取模其他除数X得到r,看对应余数集合中能否有取模后到余数r。那么我们的主要问题就是 什么条件是最优条件? 怎么判断取模后的余数在对应余数集合里?
最优条件:
首先我们知道,一个除法运算中,余数一定要比除数小才行,所以,如果我们选择的那个条件中除数小,对于其他条件除数比它更大的,我们就可能需要枚举更多的商(倍数)才能符合所有条件,举个例子,假设除数5和1e6,如果选择除数为5的条件,N = 5i +
,选除数为1e6的条件有N = 1e6i +
,我们可以发现,如果我们选择了前者作为最优条件那么很大范围内很难满足mod 1e6后的余数在后者条件到余数集合中,因为N至少得够大才行,N如果太小在怎么mod 1e6 余数都小于后者条件中最小到余数,这样就会枚举更多次数,所以第一个原则,选择X最大的。
其次因为我们选定最优条件,不但要枚举商,还要枚举里面到余数(其实是二重for循环),所有我们到第二个原则是余数个数k是最小,综合起来就是
最大的那个条件。
判断余数在相应集合中
为了加快速度,我们直接使用set存余数,到时候直接查找就可以了
code:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxc = 15;
const int maxk = 105;
const int limit = 10000;
ll total;
int C,S,X[maxc],k[maxc];//C代表条件数,S代表题目要求到解个数,X[]除数,k[]代表每个条件的可能余数个数
int bestc,Y[maxc][maxk];//bestc代表最佳条件,Y记录每个条件的每个余数是几
set<int> vis[maxc];//存放所有余数用于快速查找
void init(){
total = 1;
bestc = 0;
for(int i = 0; i < C; i++){
scanf("%d%d",&X[i],&k[i]);
total *= k[i];//将每个条件到余数个数相乘得到所有可能的组合数
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;//选择最优条件
}
}
void solve_Enum(int s){//当可能组合种类很多时直接枚举
for(int i = 0; i < C; i++){
if(i == s) continue;
vis[i].clear();
for(int j = 0; j < k[i]; j++){
vis[i].insert(Y[i][j]);
}
}
for(int t = 0; S; t++){//从小到大枚举商
for(int i = 0; i < k[s]; i++){//枚举最优条件的每个余数
ll n = (ll)X[s] * t + Y[s][i];//计算出被除数
if(n == 0) continue;
bool flag = true;
//下面判断得到的数是否满足所有条件
for(int c = 0; c < C; c++){
if(c == s) continue;
if(!vis[c].count(n%X[c])){//当前条件下没有这个余数说明这个
//数不符合条件
flag = false;
break;
}
}
if(flag){
printf("%lld\n",n);
if(--S == 0) break;
}
}
}
}
int a[maxc];//中国剩余定理中存余数到数组
vector<int>sol;//存放可能的满足所有条件的余数
ll ex_gcd(ll a,ll b,ll &x,ll &y){
if(b == 0){
x = 1;
y = 0;
return a;
}
ll gcd = ex_gcd(b,a%b,y,x);
y -= a / b * x;
return gcd;
}
int CRT(int n,int m[]){//中国剩余定理模板
ll M = 1,y,R,d;
ll x = 0;
for(int i = 0; i < n; i++)
M *= m[i];
for(int i = 0; i < n; i++){
ll w = M / m[i];
d = ex_gcd(m[i],w,y,R);
x = (x + R * w * a[i] % M) % M;
}
return (x + M) % M;
}
void dfs(int dep){
if(dep == C)
sol.push_back(CRT(C,X));
else{
for(int i = 0; i < k[dep]; i++){//通过深搜枚举余数的不同组合方式
a[dep] = Y[dep][i];
dfs(dep+1);
}
}
}
void solve_CRT(){//中国剩余定理方式解决
sol.clear();
dfs(0);
sort(sol.begin(),sol.end());
ll M = 1;
for(int i = 0; i < C; i++)
M *= X[i];//求出最小公倍数
for(int i = 0; S; i++){
for(int j = 0; j < sol.size(); j++){
ll n = M * i + sol[j];//枚举出n
if(n > 0){
printf("%lld\n",n);
if(--S == 0) break;
}
}
}
}
int main(){
while(scanf("%d%d",&C,&S) != EOF){
if(C == 0 && S == 0) break;
init();
if(total > limit)
solve_Enum(bestc);
else
solve_CRT();
puts("");
}
return 0;
}