排列组合
刚拿到书,巫泽庆译,第二版,这两道题是入门的两道,准备吃饭,走在路上想能不能。。
题1、有放回抽取小球,箱子有n个球,均写有数字,问抽四次能否抽到和为m,n个球上的数字给出。
Sample Input : n=3,m=10,k={1,3,5}
Ouput : Yes (1,1,3,5)
很简单的枚举,用四个for循环枚举暴力即可。
也可以用递归:
bool win = false; int k[51]; int m,n; void check(int n1,int n2,int n3,int n4) {
// 处理及退出条件
if(k[n1]+k[n2]+k[n3]+k[n4]==m) win=true;
if(win) return;
cout << "-> " << n1 << " " << n2 << " " << n3 << " " << n4 << endl;
if(n4<n-1) check(n1,n2,n3,n4+1); // 0000,0001,0002,0003 for d=0:3
if(n3<n-1) check(n1,n2,n3+1,0); // 0010,0011,0012,0013 for c=0:3
if(n2<n-1) check(n1,n2+1,0,0); // 0100-0103,0110-0113,0120-0123,0130-0133 for b=0:3
if(n1<n-1) check(n1+1,0,0,0); // 1000-1333,2000-2333,3000-3333 for a=0:3
}
int main()
{
cin >> n >> m;
for(int i=0;i<n;++i) cin >> k[i];
check(0,0,0,0);
cout << win << endl;
return 0;
}
拓展到取cnt个球,将n1-n4放在数组pre[]里面,尝试改上面的代码
int cnt; vector<int> pre; void check_all() { // 4 number , if there are cnt numbers, the permutation is in per[] // get sum ,and check if sum==m // if(win) return; cnt = pre.size(); for(int i=0;i<cnt;++i) cout << pre[i] <<" "; cout << endl; for(int i=cnt-1;i>=0;--i) { if(pre[i]<cnt) { for(int j=cnt-1;j>i;--j) pre[j]=1; pre[i]++; check_all(); } } } int main() { cin >> cnt; for(int i=0;i<cnt;++i) pre.push_back(1); check_all(); return 0; }
刚开始写不出来的时候,可以考虑写最简单的情况,同理,如果看不懂别人写的排列组合,不妨设定几个值(如n取3),然后手动模拟写代码。
[组合 1.6.1] 从1-4取出3个。
123 124 234
最简单的是:
for(int i=0;i<n;++i) for(int j=i+1;j<n;++j) for(int k=j+1;j<n;++k) { [ i j k ]即是一个组合 } // i<j<k保证不重复
仿照第一题我写了递归的,
void check(int n1,int n2,int n3) { cout << n << "-> " << n1 << " " << n2 << " " << n3 << endl; if(n3<n) check(n1,n2,n3+1); if(n2<n-1) check(n1,n2+1,n2+2); if(n1<n-2) check(n1+1,n1+2,n1+3); cout << "end" << endl; /*标记,更好理解程序跑到哪里了*/ }
结果(错误的)
4-> 1 2 3
4-> 1 2 4
4-> 1 3 4
4-> 2 3 4
// 应该结束了,但是下面还有
en
en
4-> 2 3 4
en
en
4-> 1 3 4
4-> 2 3 4
en
en
4-> 2 3 4
en
en
正确结果是:
void check(int n1,int n2,int n3) { cout << n << "-> " << n1 << " " << n2 << " " << n3 << endl; if(n3<n) check(n1,n2,n3+1); else if(n2<n-1) check(n1,n2+1,n2+2); else if(n1<n-2) check(n1+1,n1+2,n1+3); cout << "end" << endl; } int main() { cin >> n; check(1,2,3); return 0; }
改成任意多个,只需要把n放进数组。。
接下来继续考虑第一道题,要枚举n^4个数字,能不能改进。
考虑n4=m-n1-n2-n3,所以我们枚举n^3个数字,然后查找一个数字即可,查找最快的是log(n)
然后再想(n3+n4)=m-(n1+n2),只要实现枚举除n^2个数字,然后预先求和放在数组中,复杂度是n^2log(n)+n^2+nlog(n) (枚举搜索,枚举求和,排序)
这里是四个循环的一步步优化,如果思路上想不到怎么优化,可以从复杂的程序中尤其是for循环中着手,看能不能减少一个循环,或者把n改成logn