369 – Combinations


A combinations problem. Given N and R, find NCR.
There’s a variety of ways; The first two solutions are straight-forward computation of ^nC_r = {n(n-1)(n-2)...(n-r+1) \over r(r-1)(r-2)...1} . The last two have some tricks:

  • A naive Java solution that uses big arithmetic:
  • import java.math.*;
    import java.util.*;
    
    public class Main {
    
    	private static BigInteger C(BigInteger n, BigInteger r) {
    		r = (r.compareTo(n.subtract(r)) > 0) ? n.subtract(r) : r;
    		BigInteger num = BigInteger.ONE, den = BigInteger.ONE;
    		while (!r.equals(BigInteger.ZERO)) {
    			num = num.multiply(n); den = den.multiply(r);
    			r = r.subtract(BigInteger.ONE); n = n.subtract(BigInteger.ONE);
    		}
    		return num.divide(den);
    	}
    
    	public static void main(String[] args) {
    		Scanner s = new Scanner(System.in);
    		while (s.hasNext()) {
    			BigInteger n = s.nextBigInteger(), r = s.nextBigInteger();
    			if (n.equals(BigInteger.ZERO) && r.equals(BigInteger.ZERO))
    				break;
    			System.out.printf("%s things taken %s at a time is %s exactly.\n", n, r, C(n, r));
    		}
    	}
    }
    
  • A similar C++ solution:
  • #include <cstdio>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    
    unsigned long long C(int n, int r) {
    	r = min(n - r, r);
    	long long out = 1;
    	for (int i = 1; i <= r; ++i) {
    		out *= n - r + i;
    		out /= i;
    	}
    	return out;
    }
    
    int main() {
    	long long n, r;
    	while (scanf("%lld %lld", &n, &r) == 2 && n && r)
    		printf("%lld things taken %lld at a time is %lld exactly.\n", n, r, C(n, r));
    	return 0;
    }
    
  • A slower, but totally accepted, solution reduces the computations as you were doing it by hand; We have two arrays, one for the numerator and another for the denominator. We do a 2D loop to reduce the numerator with respect to the denominator and hence the answer. C++ implementation:
  • #include <cstdio>
    #include <cmath>
    #include <algorithm>
    #include <cstring>
    using namespace std;
    
    #define REDUCE(a, b) { int g = gcd(a, b); a /= g; b /= g; }
    
    int num[100], den[100];
    int numI, denI;
    
    long long gcd(long long a, long long b) {
    	long long t;
    	while (a) { t = a; a = b % a; b = t; }
    	return b;
    }
    
    long long C(int n, int r) {
    	// fill arrays
    	r = min(n - r, r);
    	numI = denI = 0;
    	while (r) { num[numI++] = n--; den[denI++] = r--; }
    	// reduce & compute
    	long long out = 1;
    	for (int i = 0; i < numI; ++i) {
    		for (int j = 0; j < denI; ++j)
    			REDUCE(num[i], den[j]);
    		out *= num[i];
    	}
    	return out;
    }
    
    int main() {
    	long long n, r;
    	while (scanf("%lld %lld", &n, &r) == 2 && n && r)
    		printf("%lld things taken %lld at a time is %lld exactly.\n", n, r, C(n, r));
    	return 0;
    }
    
  • The last solution has the same complexity as the first two. It uses Pascal’s Triangle for computing combinations. C++ implementation:
  • #include <cstdio>
    using namespace std;
    
    #define N 128
    
    long long pascalTri[N * (N + 1) / 2];
    
    void fillPascal() {
    	for (int l = 0, lStart = 0; l < N; ++l, lStart += l) {
    		pascalTri[lStart] = pascalTri[lStart + l] = 1;
    		for (int i = 1; i < l; ++i)
    			pascalTri[lStart + i] = pascalTri[lStart + i - l] + pascalTri[lStart + i - l - 1];
    	}
    }
    
    int main() {
    	fillPascal();
    	long long n, r;
    	while (scanf("%lld %lld", &n, &r) == 2 && n && r)
    		printf("%lld things taken %lld at a time is %lld exactly.\n", n, r, pascalTri[n * (n + 1) / 2 + r]);
    	return 0;
    }