トップページに戻る    次の競技プログラミングの問題へ    前の競技プログラミングの問題へ

川添さん問題03 ステップ・アップ・サム

■■■問題■■■

自然数nを、2つ以上の連続する自然数の和で表すことを考えましょう。

例えば、18は 5 + 6 + 7 や 3 + 4 + 5 + 6 と表すことができます。
同様に、15は 7 + 8 や 4 + 5 + 6 や 1 + 2 + 3 + 4 + 5 と表すことができます。
いずれもこれら以外の表し方は存在しません。

この表し方における最も小さな数 (上で赤字で記した数のことです) を、
全ての表し方について和をとったものを F(n) と定義します。

例えば F(18) = 5 + 3 = 8、F(15) = 7 + 4 + 1 = 12 です。
同様に、F(105) = 139、F(256) = 0、F(945) = 1698 であることが確かめられます。

標準入力から、自然数 n (1 <= n <= 100億) が与えられます。
標準出力に F(n) の値を出力するプログラムを書いてください。


C#のソース

using System;
using System.Collections.Generic;

class Program
{
    static string InputPattern = "Input2";

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

        if (InputPattern == "Input1") {
            WillReturn.Add("18");
            //8
        }
        else if (InputPattern == "Input2") {
            WillReturn.Add("15");
            //12
        }
        else if (InputPattern == "Input3") {
            WillReturn.Add("105");
            //139
        }
        else if (InputPattern == "Input4") {
            WillReturn.Add("256");
            //0
        }
        else if (InputPattern == "Input5") {
            WillReturn.Add("945");
            //1698
        }
        else {
            string wkStr;
            while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
        }
        return WillReturn;
    }

    static void Main()
    {
        List<string> InputList = GetInputList();
        long N = long.Parse(InputList[0]);

        long Answer = 0;

        //項数でループ
        for (long Kousuu = 2; ; Kousuu++) {
            //1から始めたとしても、オーバーならBreak
            long wkSum = (Kousuu * (Kousuu + 1)) / 2;
            if (wkSum > N) break;

            long Syokou;
            if (ExecNibunhou(N, Kousuu, out Syokou)) {
                Answer += Syokou;
            }
        }
        Console.WriteLine(Answer);
    }

    //S = 項数 * (初項+初項+項数-1) /2
    //は、初項に対して単調増加なので、初項を2分法で探す
    static bool ExecNibunhou(long pN, long pKousuu, out long pSyokou)
    {
        pSyokou = -1;
        long L = 1;
        long R = pN / 2; //最低項数が2なので右端はpN/2
        while (L <= R) {
            pSyokou = (L + R) / 2;

            long wkSum = pKousuu * (pSyokou * 2 + pKousuu - 1) / 2;
            if (wkSum == pN) {
                Console.WriteLine("初項={0},項数={1},和={2}", pSyokou, pKousuu, pN);
                return true;
            }
            if (wkSum < pN)
                L = pSyokou + 1;
            else R = pSyokou - 1;
        }
        return false;
    }
}


C#でのデバッグ情報付の実行結果

初項=7,項数=2,和=15
初項=4,項数=3,和=15
初項=1,項数=5,和=15
12


C++のソース

#include <stdio.h>
#include <string>

bool ExecNibunhou(long long pN, long long pKousuu, long long &pSyokou);

std::string InputPattern = "Input2";

long long GetInput()
{
    if (InputPattern == "Input1") {
        return 18;
        //8
    }
    else if (InputPattern == "Input2") {
        return 15;
        //12
    }
    else if (InputPattern == "Input3") {
        return 105;
        //139
    }
    else if (InputPattern == "Input4") {
        return 256;
        //0
    }
    else if (InputPattern == "Input5") {
        return 945;
        //1698
    }
    else {
        long long ScanVal;
        int Dummy = scanf("%lld",&ScanVal);
        return ScanVal;
    }
}

int main()
{
    long long N = GetInput();
    long long Answer = 0;

    //項数でループ
    for (long long Kousuu = 2; ; Kousuu++) {
        //1から始めたとしても、オーバーならBreak
        long long wkSum = (Kousuu * (Kousuu + 1)) / 2;
        if (wkSum > N) break;

        long long Syokou;
        if (ExecNibunhou(N, Kousuu, Syokou)) {
            Answer += Syokou;
        }
    }
    printf("%lld\n",Answer);
}

//S = 項数 * (初項+初項+項数-1) /2
//は、初項に対して単調増加なので、初項を2分法で探す
bool ExecNibunhou(long long pN, long long pKousuu, long long &pSyokou)
{
    pSyokou = -1;
    long long L = 1;
    long long R = pN / 2; //最低項数が2なので右端はpN/2
    while (L <= R) {
        pSyokou = (L + R) / 2;

        long long wkSum = pKousuu * (pSyokou * 2 + pKousuu - 1) / 2;
        if (wkSum == pN) {
            printf("初項=%lld,項数=%lld,和=%lld\n",pSyokou, pKousuu, pN);
            return true;
        }
        if (wkSum < pN)
            L = pSyokou + 1;
        else R = pSyokou - 1;
    }
    return false;
}


解説

項数が3で初項が1なら、S = 1+2+3
項数が5で初項が10なら、S = 10+11+12+13+14
となり、一般化すると、等差数列の和の公式を使って
S = 項数 * (初項+末項) /2
  = 項数 * (初項+初項+項数-1) /2
となります。

Sは固定ですので、項数をループさせつつ、
数式を満たす初項が存在するかを、2分法で探してます。