Package

Source Code of p172

/*
* Solution to Project Euler problem 172
* By Nayuki Minase
*
* http://nayuki.eigenstate.org/page/project-euler-solutions
* https://github.com/nayuki/Project-Euler-solutions
*/

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;


public final class p172 implements EulerSolution {
 
  public static void main(String[] args) {
    System.out.println(new p172().run());
  }
 
 
  // Highly customizable!
  private static final int LENGTH = 18;    // >= 1
  private static final int MAX_COUNT = 3// >= ceiling(LENGTH / BASE), else the result is 0
  private static final int BASE = 10;      // >= 2
 
 
  /*
   * Let's explain how to solve this problem mostly by examples rather than by rigorous arguments.
   *
   * We want to generate all the sequences of 18 decimal digits where each digit value (from '0' to '9') is used 0 to 3 times.
   * We can partition the set of all possible sequences by considering, for each sequence, the multiset of digit value repetition counts.
   * For example, the number 111222333444555666 has '1' used 3 times, '2' used 3 times, ..., and '6' used 3 times.
   * When we look at these repetition counts, we see that 3 + 3 + 3 + 3 + 3 + 3 = 18. We always need them to sum to 18 ('LENGTH').
   *
   * There are sequences with other repetition counts too. For example: 121212333444567890, the rep counts are 1 + 3 + 3 + 3 + 3 + 1 + 1 + 1 + 1 + 1.
   * In fact, there are exactly 17 ways (partitions) to express 18 as an unordered sum of 10 terms with each term from 0 to 3:
   * - 3 + 3 + 3 + 3 + 3 + 3 + 0 + 0 + 0 + 0
   * - 3 + 3 + 3 + 3 + 3 + 2 + 1 + 0 + 0 + 0
   * - 3 + 3 + 3 + 3 + 3 + 1 + 1 + 1 + 0 + 0
   * - 3 + 3 + 3 + 3 + 2 + 2 + 2 + 0 + 0 + 0
   * - 3 + 3 + 3 + 3 + 2 + 2 + 1 + 1 + 0 + 0
   * - 3 + 3 + 3 + 3 + 2 + 1 + 1 + 1 + 1 + 0
   * - 3 + 3 + 3 + 3 + 1 + 1 + 1 + 1 + 1 + 1
   * - 3 + 3 + 3 + 2 + 2 + 2 + 2 + 1 + 0 + 0
   * - 3 + 3 + 3 + 2 + 2 + 2 + 1 + 1 + 1 + 0
   * - 3 + 3 + 3 + 2 + 2 + 1 + 1 + 1 + 1 + 1
   * - 3 + 3 + 2 + 2 + 2 + 2 + 2 + 2 + 0 + 0
   * - 3 + 3 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 0
   * - 3 + 3 + 2 + 2 + 2 + 2 + 1 + 1 + 1 + 1
   * - 3 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 1 + 0
   * - 3 + 2 + 2 + 2 + 2 + 2 + 2 + 1 + 1 + 1
   * - 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 0
   * - 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 1 + 1
   * For example, the number 912349441125323088 is associated with the partition (3 + 3 + 3 + 3 + 2 + 2 + 1 + 1 + 0 + 0),
   * because '1' is used 3 times, '2' is used 3 times, '3' is used 3 times, '8' is used 2 times, '9' is used 2 times,
   * '0' is used 1 time, '5' is used 1 time, '6' is used 0 times, and '7' is used 0 times.
   *
   * For each partition, we want to take the 10 decimal digit values and assign them to the terms of the partition. Here is one example assignment:
   *   Frequency:   3 3 3 3 | 2 2 | 1 1 | 0 0
   *   Digit value: 1 2 3 4 | 8 9 | 0 5 | 6 7
   * But note that order does not matter if the frequency is the same - for example, this means the same as above:
   *   Frequency:   3 3 3 3 | 2 2 | 1 1 | 0 0
   *   Digit value: 4 1 3 2 | 8 9 | 5 0 | 7 6
   *
   * For a given partition, how many ways are there to assign digit values to the frequencies?
   * Considering the frequencies and the digit values each as a length-10 sequence (like above),
   * we know that there are 10! raw ways to arrange the digit values. But order within a frequency does not matter.
   * So we divide by the factorial of the repetition count of each frequency. In the example above, the answer is 10! / (4! 2! 2! 2!).
   * (Note that this is a multinomial coefficient.)
   *
   * Now that we have a partition and a digit value assignment to the frequencies, we can generate permutations.
   * For the example above, one possible sequence (and the lexicographically lowest) is 011122233344458899 (length 18).
   * If we permute this sequence, the partition and digit-frequency assignments will remain the same.
   * So we want to count how many permutations this sequence has.
   *
   * Given a partition and a digit-frequency assignment, how many sequences have this classification?
   * Because there are 18 digits, there are 18! raw arrangements of digits for the sequence.
   * But for each digit value, it is repeated k times, so we need to divide by k! to suppress identical-looking arrangements.
   * In this example, there are 18! / (3! 3! 3! 3! 2! 2! 2! 2! 2! 2!) arrangements.
   *
   * Now, all of the arguments above have no made use of the specific digit values, so there is a certain symmetry in the set of desired sequences.
   * In particular, this means exactly 9/10th of all items have a leading zero, hence we multiply by 9/10 to get the final answer.
   */
  public String run() {
    BigInteger ways = partitionAndCount(LENGTH, MAX_COUNT, new ArrayList<Integer>());
   
    // Multiply by (base - 1) / base to discount sequences with leading zeros
    BigInteger BASE_BI = BigInteger.valueOf(BASE);
    ways = ways.multiply(BASE_BI.subtract(BigInteger.ONE));
    ways = divideExactly(ways, BASE_BI);
   
    return ways.toString();
  }
 
 
  // Expresses 'LENGTH' as a sum of 'BASE' non-increasing terms, where terms to be added are in the range [0, max].
  // e.g. partitionAndCount(7, 2, [3, 3, 2, 2, 1]) asks us to express 18 as a sum of 5 more terms,
  // where the new terms have a sum of 7 and each is no greater than 2 and all terms are non-increasing.
  private BigInteger partitionAndCount(int sum, int max, List<Integer> terms) {
    if (terms.size() == BASE) {
      if (sum == 0)
        return countWays(terms);
      else
        return BigInteger.ZERO;
     
    } else {
      BigInteger result = BigInteger.ZERO;
      for (int i = Math.min(max, sum); i >= 0; i--) {
        terms.add(i);
        result = result.add(partitionAndCount(sum - i, i, terms));
        terms.remove(terms.size() - 1);
      }
      return result;
    }
  }
 
 
  private BigInteger countWays(List<Integer> freqs) {
    // The number of times each frequency value occurs
    int[] histogram = new int[MAX_COUNT + 1];
    for (int x : freqs)
      histogram[x]++;
   
    // Multinomial coefficient: BASE! / (histogram[0]! * histogram[1]! * ...)
    BigInteger ways = Library.factorial(BASE);
    for (int x : histogram)
      ways = ways.divide(Library.factorial(x));
   
    // Multinomial coefficient: LENGTH! / (freqs[0]! * freqs[1]! * ...)
    ways = ways.multiply(Library.factorial(LENGTH));
    for (int x : freqs)
      ways = ways.divide(Library.factorial(x));
   
    return ways;
  }
 
 
  private static BigInteger divideExactly(BigInteger x, BigInteger y) {
    BigInteger[] temp = x.divideAndRemainder(y);
    if (temp[1].signum() != 0)
      throw new IllegalArgumentException("Not divisible");
    return temp[0];
  }
 
}
TOP

Related Classes of p172

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.