トップページに戻る    次のC#のサンプルへ    前のC#のサンプルへ

Problem158 左隣の文字より辞書順で後になる文字

問題

26文字のアルファベットから3個の異なった文字を取ると, 長さ3の文字列を作ることができる.
例えば, 'abc', 'hat', 'zyx'となる.

この3つの例について調べると, 'abc'は左隣の文字より辞書順で後になる文字が2個ある.
'hat'では, 左隣の文字より辞書順で後になる文字がちょうど1個となり,
'zyx'では0個となる.

左隣の文字より辞書順で後になる文字がちょうど1個となるような長さ3の文字列は全部で10400個存在する.

いま, n <= 26個の異なった文字について考える.
nについて, p(n) を左隣の文字より辞書順で後になる文字がちょうど1個となるような長さnの文字列の個数であるとする.

p(n) の最大値を求めよ.


ソース

using System;

class Program
{
    static void Main()
    {
        long Answer = long.MinValue;
        for (int I = 1; I <= 26; I++) {
            long wkLong = Solve(I);
            Console.WriteLine("pN({0})={1}", I, wkLong);
            if (Answer < wkLong)
                Answer = wkLong;
        }
        Console.WriteLine("解は{0}", Answer);
    }

    static long Solve(int pN)
    {
        long WillReturn = 0;

        //単調増加になる文字の組み合わせ
        for (char I = 'A'; I <= 'Z'; I++) {
            for (char J = (char)(I + 1); J <= 'Z'; J++) {
                //単調増加になった位置
                for (int K = 2; K <= pN; K++) {
                    WillReturn += DerivePatternCnt(I, J, K, pN);
                }
            }
        }
        return WillReturn;
    }

    //単調増加な文字の組み合わせと増加した位置を引数として、場合の数を返す
    static long DerivePatternCnt(char pChar1, char pChar2, int pK, int pN)
    {
        bool HasLeftSpace = pK > 2;
        bool HasRightSpace = pK < pN;

        int LeftKouhoCnt = 'Z' - pChar1;
        LeftKouhoCnt--; //pChar2の分
        int RightKouhoCnt = pChar2 - 'A';
        RightKouhoCnt--; //pChar1の分

        int LeftNeedCnt = pK - 2;
        int RightNeedCnt = pN - pK;

        if (HasLeftSpace == false && HasRightSpace == false) {
            return 1;
        }
        if (HasLeftSpace == false && HasRightSpace) {
            if (RightNeedCnt <= RightKouhoCnt) return DeriveCombi(RightKouhoCnt, pN - pK);
            return 0;
        }
        if (HasLeftSpace && HasRightSpace == false) {
            if (LeftNeedCnt <= LeftKouhoCnt) return DeriveCombi(LeftKouhoCnt, pK - 2);
            return 0;
        }
        
        //Iの左区間でも、Jの右区間でも使える文字の数の、左区間での使用数で場合分け
        long TotalCnt = 0;
        int CommonCnt = pChar2 - pChar1 - 1;
        for (int L = 0; L <= Math.Min(CommonCnt, LeftNeedCnt); L++) {
            //Iの左区間の場合の数
            int RestLeftKouhoCnt = 'Z' - pChar2;
            int RestLeftNeedCnt = LeftNeedCnt - L;
            if (L < LeftNeedCnt && RestLeftKouhoCnt < RestLeftNeedCnt) continue;
            long LeftPatternCnt = DeriveCombi(RestLeftKouhoCnt, RestLeftNeedCnt);

            //Jの右区間の場合の数
            int wkRightKouhoCnt = RightKouhoCnt - L;
            if (wkRightKouhoCnt < RightNeedCnt) continue;
            long RightPatternCnt = DeriveCombi(wkRightKouhoCnt, RightNeedCnt);

            long wkCnt = DeriveCombi(CommonCnt, L) * LeftPatternCnt * RightPatternCnt;
            TotalCnt += wkCnt;
        }
        return TotalCnt;
    }

    //nCrを求める
    static long DeriveCombi(int pN, int pR)
    {
        long WillReturn = 1;
        pR = Math.Min(pR, pN - pR);

        for (int I = pN - pR + 1; I <= pN; I++) {
            WillReturn *= I;
        }
        for (int I = 2; I <= pR; I++) {
            WillReturn /= I;
        }
        return WillReturn;
    }
}


実行結果

省略
pN(15)=253047192320
pN(16)=348019565465
pN(17)=409484775700
pN(18)=409511334375
pN(19)=344863490400
pN(20)=241408817650
pN(21)=137949211400
pN(22)=62704500950
pN(23)=21810318400
pN(24)=5452587075
pN(25)=872414556
pN(26)=67108837
解は409511334375


解説

単調増加になった時の文字のペアと座標の組み合わせごとに、
場合の数を求めてます。