AtCoderの企業コンテスト    次の企業コンテストの問題へ    前の企業コンテストの問題へ

HHKB 2020 D Squares


問題へのリンク


C#のソース

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static string InputPattern = "InputX";

    static List<string> GetInputList()
    {
        var WillReturn = new List<string>();

        if (InputPattern == "Input1") {
            WillReturn.Add("3");
            WillReturn.Add("3 1 2");
            WillReturn.Add("4 2 2");
            WillReturn.Add("331895368 154715807 13941326");
            //20
            //32
            //409369707
        }
        else {
            string wkStr;
            while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
        }
        return WillReturn;
    }

    const long Hou = 1000000007;

    static long mN;
    static long mA;
    static long mB;
    static long UB;

    // Aだけを配置するとして、配置可能な最大添字
    static long mMaxIndA;

    // Bだけを配置するとして、配置可能な最大添字
    static long mMaxIndB;

    static void Main()
    {
        List<string> InputList = GetInputList();

        long[] wkArr = { };
        Action<string> SplitAct = pStr =>
            wkArr = pStr.Split(' ').Select(pX => long.Parse(pX)).ToArray();

        foreach (string EachStr in InputList.Skip(1)) {
            SplitAct(EachStr);
            mN = wkArr[0];
            mA = wkArr[1];
            mB = wkArr[2];
            long Result = Solve();
            Console.WriteLine(Result);
        }
    }

    static long Solve()
    {
        // 配置不能な場合
        if (mA + mB > mN) return 0;

        UB = mN;

        // 簡単のため、すり替え
        var ABList = new List<long>();
        ABList.Add(mA);
        ABList.Add(mB);
        mA = ABList.Max();
        mB = ABList.Min();

        mMaxIndA = UB - mA;
        mMaxIndB = UB - mB;

        // 場合1 Aの左端 <= Bの左端 の場合
        //for (long I = 0; I <= mMaxIndA; I++) {
        //    Console.WriteLine("AのIndが{0}の場合、Bは{1}通り", I, DerivePlaceCnt1(I));
        //}
        long MinPattern1 = DerivePlaceCnt1(mMaxIndA);
        long MaxPattern1 = DerivePlaceCnt1(0);
        long Result1 = ExecNibunhou1(MaxPattern1);
        //Console.WriteLine("MinPattern1={0}", MinPattern1);
        //Console.WriteLine("MaxPattern1={0}", MaxPattern1);
        //Console.WriteLine("可能なBの配置数が最大なのは{0}まで", Result1);
        long MaxCnt1 = Result1 + 1;
        long Pattern1 = MaxCnt1 * MaxPattern1;
        long Syokou1 = MaxPattern1 - 1;
        Pattern1 += DeriveTousaSuuretuSum(Syokou1, MinPattern1, -1, Hou);
        Pattern1 %= Hou;
        //Console.WriteLine("Pattern1={0}", Pattern1);

        // 場合2 Aの左端 > Bの左端 の場合
        //for (long I = 0; I <= mMaxIndA; I++) {
        //    Console.WriteLine("AのIndが{0}の場合、Bは{1}通り", I, DerivePlaceCnt2(I));
        //}
        long MinPattern2 = DerivePlaceCnt2(0);
        long MaxPattern2 = DerivePlaceCnt2(mMaxIndA);
        long Result2 = ExecNibunhou2(MaxPattern2);
        //Console.WriteLine("MinPattern2={0}", MinPattern2);
        //Console.WriteLine("MaxPattern2={0}", MaxPattern2);
        //Console.WriteLine("可能なBの配置数が最大なのは{0}から", Result2);
        long MaxCnt2 = mMaxIndA - Result2 + 1;
        long Pattern2 = MaxCnt2 * MaxPattern2;
        long Syokou2 = MaxPattern2 - 1;
        Pattern2 += DeriveTousaSuuretuSum(Syokou2, MinPattern2, -1, Hou);
        Pattern2 %= Hou;
        //Console.WriteLine("Pattern2={0}", Pattern2);

        long Answer = 1;
        long Prod1 = (mMaxIndA + 1) * (mMaxIndA + 1);
        Prod1 %= Hou;
        Answer *= Prod1;
        Answer %= Hou;

        long Prod2 = (mMaxIndB + 1) * (mMaxIndB + 1);
        Prod2 %= Hou;
        Answer *= Prod2;
        Answer %= Hou;

        long PatternSum = Pattern1 + Pattern2;
        PatternSum %= Hou;
        Answer -= PatternSum * PatternSum;
        Answer %= Hou;

        if (Answer < 0) Answer += Hou;

        return Answer;
    }

    // Aの左端を引数として、可能なBの配置数を求める
    // (Aの左端 <= Bの左端 の場合)
    static long DerivePlaceCnt1(long pAInd)
    {
        long pBInd = pAInd + mA - 1;
        pBInd = Math.Min(pBInd, mMaxIndB);

        if (pAInd <= pBInd) {
            return pBInd - pAInd + 1;
        }
        return 0;
    }

    // 二分法で、Bの配置数が最大なIndの最大値を求める
    // (Aの左端 <= Bの左端 の場合)
    static long ExecNibunhou1(long pMaxVal)
    {
        long L = 0;
        long R = UB;

        while (L + 1 < R) {
            long Mid = (L + R) / 2;

            long MidVal = DerivePlaceCnt1(Mid);
            if (MidVal == pMaxVal) {
                L = Mid;
            }
            else {
                R = Mid;
            }
        }
        return L;
    }

    // Aの左端を引数として、可能なBの配置数を求める
    // (Aの左端 > Bの左端 の場合)
    static long DerivePlaceCnt2(long pAInd)
    {
        // Bの長さが1なら不可
        if (mB == 1) return 0;

        long MaxLeft = pAInd - 1;
        if (MaxLeft < 0) return 0;

        long MinLeft = pAInd + 1 - mB;
        MinLeft = Math.Max(0, MinLeft);

        return MaxLeft - MinLeft + 1;
    }

    // 二分法で、Bの配置数が最大なIndの最大値を求める
    // (Aの左端 > Bの左端 の場合)
    static long ExecNibunhou2(long pMaxVal)
    {
        long L = 0;
        long R = UB;

        while (L + 1 < R) {
            long Mid = (L + R) / 2;

            long MidVal = DerivePlaceCnt2(Mid);
            if (MidVal == pMaxVal) {
                R = Mid;
            }
            else {
                L = Mid;
            }
        }
        return R;
    }

    // 初項と末項と公差と法を引数として、等差数列の和を返す
    static long DeriveTousaSuuretuSum(long pSyokou, long pMakkou, long pKousa, long pHou)
    {
        long KouCnt = (pMakkou - pSyokou) / pKousa + 1;
        long wkSum = (pSyokou + pMakkou) % Hou;
        wkSum %= Hou;
        long Result = wkSum * KouCnt;
        Result %= Hou;
        Result *= DeriveGyakugen(2);
        Result %= Hou;
        return Result;
    }

    // 引数の逆元を求める
    static long DeriveGyakugen(long pLong)
    {
        return DeriveBekijyou(pLong, Hou - 2, Hou);
    }

    // 繰り返し2乗法で、(NのP乗) Mod Mを求める
    static long DeriveBekijyou(long pN, long pP, long pM)
    {
        long CurrJyousuu = pN % pM;
        long CurrShisuu = 1;
        long WillReturn = 1;

        while (true) {
            // 対象ビットが立っている場合
            if ((pP & CurrShisuu) > 0) {
                WillReturn = (WillReturn * CurrJyousuu) % pM;
            }

            CurrShisuu *= 2;
            if (CurrShisuu > pP) return WillReturn;
            CurrJyousuu = (CurrJyousuu * CurrJyousuu) % pM;
        }
    }
}


解説

N=8で
Aの長さ5
Bの長さ3
とすると

操作X [0,8]から、長さ5の区間と、長さ3の区間を選択する。

として、
操作Xを2回行う。
2回とも区間がOverLapするのは何通りか?

が解ければ、余事象の件数が分かります。

区間の選び方が何通りあるかは、二分法で最大値の連続区間と
減少区間を調べてから
等差数列の和の公式で、求めることができます。