import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

public class Euler987 {
    private static final int STARTS = 10;
    private static final int RANKS = 13;
    private static final int MAX_STRAIGHTS = 8;
    private static final int FREE_STATE = 4;

    private static final int[][] COVER = {
            {0, 1, 2, 3, 12},
            {0, 1, 2, 3, 4},
            {1, 2, 3, 4, 5},
            {2, 3, 4, 5, 6},
            {3, 4, 5, 6, 7},
            {4, 5, 6, 7, 8},
            {5, 6, 7, 8, 9},
            {6, 7, 8, 9, 10},
            {7, 8, 9, 10, 11},
            {8, 9, 10, 11, 12},
    };

    private static final long[] FACT = {1L, 1L, 2L, 6L, 24L, 120L, 720L, 5040L, 40320L};
    private static final int[] POW5 = {1, 5, 25, 125, 625, 3125, 15625, 78125, 390625};
    private static final int[][][] SUIT_PERMS = buildSuitPerms();

    private static final class PatternData {
        int straightCount;
        int[] lastRank = new int[MAX_STRAIGHTS];
        int[] activeMask = new int[MAX_STRAIGHTS];
        int[][] entryPos = new int[RANKS][MAX_STRAIGHTS];
        int[][] nextPos = new int[RANKS][MAX_STRAIGHTS];
        int[][] currentLabels = new int[RANKS][MAX_STRAIGHTS];
        int[][] nextLabels = new int[RANKS][MAX_STRAIGHTS];
        int[] currentCount = new int[RANKS];
        int[] nextCount = new int[RANKS];
        long divisor = 1L;
    }

    private static final class WorkerCtx {
        List<int[]> patterns;
        AtomicInteger nextIndex;
        long subtotal;
    }

    private static int[][][] buildSuitPerms() {
        @SuppressWarnings("unchecked")
        List<int[]>[] perms = new ArrayList[5];
        for (int i = 0; i < perms.length; i++) {
            perms[i] = new ArrayList<>();
        }

        perms[0].add(new int[] {0, 0, 0, 0});
        for (int a = 0; a < 4; ++a) {
            perms[1].add(new int[] {a, 0, 0, 0});
            for (int b = 0; b < 4; ++b) {
                if (b == a) {
                    continue;
                }
                perms[2].add(new int[] {a, b, 0, 0});
                for (int c = 0; c < 4; ++c) {
                    if (c == a || c == b) {
                        continue;
                    }
                    perms[3].add(new int[] {a, b, c, 0});
                    for (int d = 0; d < 4; ++d) {
                        if (d == a || d == b || d == c) {
                            continue;
                        }
                        perms[4].add(new int[] {a, b, c, d});
                    }
                }
            }
        }

        int[][][] out = new int[5][][];
        for (int i = 0; i < out.length; i++) {
            out[i] = perms[i].toArray(new int[0][]);
        }
        return out;
    }

    private static PatternData buildPatternData(int[] pattern) {
        PatternData data = new PatternData();
        int[] firstRank = new int[MAX_STRAIGHTS];

        for (int rank = 0; rank < RANKS; ++rank) {
            for (int label = 0; label < MAX_STRAIGHTS; ++label) {
                data.entryPos[rank][label] = -1;
                data.nextPos[rank][label] = -1;
            }
        }

        for (int start = 0; start < STARTS; ++start) {
            data.divisor *= FACT[pattern[start]];
            int[] ranks = COVER[start];
            int mask = 0;
            for (int rank : ranks) {
                mask |= 1 << rank;
            }

            for (int k = 0; k < pattern[start]; ++k) {
                int label = data.straightCount++;
                firstRank[label] = ranks[0];
                data.lastRank[label] = ranks[4];
                data.activeMask[label] = mask;
            }
        }

        for (int rank = 0; rank < RANKS; ++rank) {
            int currentIdx = 0;
            int nextIdx = 0;
            for (int label = 0; label < data.straightCount; ++label) {
                if (((data.activeMask[label] >>> rank) & 1) != 0) {
                    data.currentLabels[rank][currentIdx++] = label;
                }
                if (firstRank[label] <= rank && rank < data.lastRank[label]) {
                    data.nextPos[rank][label] = nextIdx;
                    data.nextLabels[rank][nextIdx++] = label;
                }
            }
            data.currentCount[rank] = currentIdx;
            data.nextCount[rank] = nextIdx;

            int entryIdx = 0;
            for (int label = 0; label < data.straightCount; ++label) {
                if (firstRank[label] < rank && rank <= data.lastRank[label]) {
                    data.entryPos[rank][label] = entryIdx++;
                }
            }
        }

        return data;
    }

    private static long countPattern(int[] pattern) {
        PatternData data = buildPatternData(pattern);

        HashMap<Integer, Long> current = new HashMap<>(1024);
        current.put(0, 1L);

        for (int rank = 0; rank < RANKS; ++rank) {
            HashMap<Integer, Long> next = new HashMap<>(1024);
            int currentCount = data.currentCount[rank];
            int nextCount = data.nextCount[rank];

            for (Map.Entry<Integer, Long> entry : current.entrySet()) {
                int stateCode = entry.getKey();
                long ways = entry.getValue();

                int[] stateDigits = new int[MAX_STRAIGHTS];
                int tmp = stateCode;
                for (int i = 0; i < MAX_STRAIGHTS; ++i) {
                    stateDigits[i] = tmp % 5;
                    tmp /= 5;
                }

                long carriedCode = 0L;
                for (int idx = 0; idx < nextCount; ++idx) {
                    int label = data.nextLabels[rank][idx];
                    if (((data.activeMask[label] >>> rank) & 1) == 0) {
                        int oldPos = data.entryPos[rank][label];
                        carriedCode += (long) stateDigits[oldPos] * POW5[idx];
                    }
                }

                for (int[] suits : SUIT_PERMS[currentCount]) {
                    boolean ok = true;
                    long nextCode = carriedCode;

                    for (int idx = 0; idx < currentCount; ++idx) {
                        int label = data.currentLabels[rank][idx];
                        int suit = suits[idx];
                        int oldPos = data.entryPos[rank][label];

                        int newState = suit;
                        if (oldPos != -1) {
                            int oldState = stateDigits[oldPos];
                            newState = (oldState == FREE_STATE || oldState != suit) ? FREE_STATE : oldState;
                        }

                        if (rank == data.lastRank[label]) {
                            if (newState != FREE_STATE) {
                                ok = false;
                                break;
                            }
                        } else {
                            int outPos = data.nextPos[rank][label];
                            nextCode += (long) newState * POW5[outPos];
                        }
                    }

                    if (!ok) {
                        continue;
                    }

                    int encoded = (int) nextCode;
                    Long old = next.get(encoded);
                    next.put(encoded, old == null ? ways : old + ways);
                }
            }

            current = next;
        }

        Long result = current.get(0);
        require(result != null, "Missing terminal state");
        return Long.divideUnsigned(result, data.divisor);
    }

    private static void generatePatternsRec(int startIdx, int remaining, int[] pattern, int[] occupancy, List<int[]> out) {
        if (startIdx == STARTS) {
            if (remaining == 0) {
                out.add(pattern.clone());
            }
            return;
        }

        int[] ranks = COVER[startIdx];
        int limit = Math.min(4, remaining);
        for (int copies = 0; copies <= limit; ++copies) {
            boolean ok = true;
            for (int rank : ranks) {
                if (occupancy[rank] + copies > 4) {
                    ok = false;
                    break;
                }
            }
            if (!ok) {
                break;
            }

            pattern[startIdx] = copies;
            for (int rank : ranks) {
                occupancy[rank] += copies;
            }
            generatePatternsRec(startIdx + 1, remaining - copies, pattern, occupancy, out);
            for (int rank : ranks) {
                occupancy[rank] -= copies;
            }
        }
        pattern[startIdx] = 0;
    }

    private static List<int[]> generatePatterns(int straightCount) {
        List<int[]> out = new ArrayList<>();
        generatePatternsRec(0, straightCount, new int[STARTS], new int[RANKS], out);
        return out;
    }

    private static long countTotalSingleThread(int straightCount) {
        long total = 0L;
        for (int[] pattern : generatePatterns(straightCount)) {
            total += countPattern(pattern);
        }
        return total;
    }

    private static long countTotalParallel(int straightCount) {
        List<int[]> patterns = generatePatterns(straightCount);
        int cpuCount = Runtime.getRuntime().availableProcessors();
        if (cpuCount < 1) {
            cpuCount = 1;
        }
        int threadCount = Math.min(32, cpuCount);
        if (threadCount == 1 || patterns.size() < 8) {
            long total = 0L;
            for (int[] pattern : patterns) {
                total += countPattern(pattern);
            }
            return total;
        }

        AtomicInteger nextIndex = new AtomicInteger(0);
        WorkerCtx[] workers = new WorkerCtx[threadCount];
        Thread[] threads = new Thread[threadCount];

        for (int t = 0; t < threadCount; ++t) {
            WorkerCtx worker = new WorkerCtx();
            worker.patterns = patterns;
            worker.nextIndex = nextIndex;
            workers[t] = worker;

            threads[t] = new Thread(() -> {
                while (true) {
                    int idx = worker.nextIndex.getAndIncrement();
                    if (idx >= worker.patterns.size()) {
                        break;
                    }
                    worker.subtotal += countPattern(worker.patterns.get(idx));
                }
            });
            threads[t].start();
        }

        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Thread interrupted", e);
            }
        }

        long total = 0L;
        for (WorkerCtx worker : workers) {
            total += worker.subtotal;
        }
        return total;
    }

    private static void require(boolean condition, String message) {
        if (!condition) {
            throw new IllegalStateException(message);
        }
    }

    public static String solve() {
        require(countTotalSingleThread(1) == 10200L, "Check single straight");
        require(countTotalSingleThread(2) == 31832952L, "Check two straights");
        return Long.toUnsignedString(countTotalParallel(8));
    }

    public static void main(String[] args) {
        System.out.println(solve());
    }
}
