Official

C - 花壇の植え付け / Planting the Flower Bed Editorial by MMNMM


\(N=1\) のとき、隣り合うポイントが存在しないので、どのポイントを選んでも条件を満たします。 最も左にあるポイントと最も右にあるポイントが一致するので、これらの距離は常に \(0\) になります。 よって、\(N=1\) なら答えは \(0\) です。

以下では、\(N\ge2\) のときについて考えます。


条件を満たす選び方が存在するなら、その選び方で最も左にあるポイントをポイント \(1\) に、最も右にあるポイントをポイント \(M\) に置き換えた選び方も条件を満たします。 よって、答えは条件を満たす選び方が存在しなければ \(-1\) 、存在すれば \(D _ 1+D _ 2+\cdots+D _ {M-1}\) となります。

条件を満たす選び方が存在するかは、貪欲法を使って判定することができます。 具体的には、ポイントを先頭から確認し、直前に選んだポイントとの距離が \(K\) 以上(もしくはまだポイントをひとつも選んでいない)ならそのポイントを選ぶことを繰り返して \(N\) 個以上のポイントを選べるかどうか判定すればよいです。

証明

\(X _ i\) を、ポイント \(1\) とポイント \(i\) との距離として定めます(つまり、\(X _ i=D _ 1+D _ 2+\cdots+D _ {i-1}\) です)。 ポイントは左から順に番号付けられているので、\(X _ i\lt X _ {i+1}\) です。

貪欲法で \(L\) 個のポイントが選べたとし、選んだポイントをポイント \(I _ 1,I _ 2,\ldots,I _ L\) とします。 \(L\ge N\) なら、先頭の \(N\) 個を選べば条件を満たすように \(N\) 個のポイントを選ぶことができます。 逆に、条件を満たす \(N\) 個のポイントがあれば、\(L\ge N\) となることを示します。

条件を満たす \(N\) 個のポイントの選び方をひとつとり、ポイント \(J _ 1,J _ 2,\ldots,J _ N\) とします。 ポイントの満たす条件から、\(X _ {J _ {i+1}}-X _ {J _ i}\ge K\) が成り立ちます。 貪欲法の選び方から、\(X _ {I _ {i+1}-1}-X _ {I _ i}\lt K\le X _ {I _ {i+1}}-X _ {I _ i}\) が成り立ちます。

すべての \(1\le i\le\min\lbrace L,N\rbrace\) に対して、\(I _ i\le J _ i\) が成り立つことを示します。 \(1=I _ 1\le J _ 1\) は明らかです。 ある \(1\le i\lt\min\lbrace L,N\rbrace\) をとり、\(I _ i\le J _ i\) が成り立つとします。 すると、\(X _ {I _ {i+1}-1}-X _ {J _ i}\le X _ {I _ {i+1}-1}-X _ {I _ i}\lt K\le X _ {J _ {i+1}}-X _ {J _ i}\) なので \(X _ {I _ {i+1}-1}\lt X _ {J _ {i+1}}\) となり、\(I _ {i+1}-1\lt J _ {i+1}\) が成り立ちます。 これらから、すべての \(1\le i\le\min\lbrace L,N\rbrace\) に対して、\(I _ i\le J _ i\) が成り立つことが示せました。

このことから、貪欲法においてこれまでに選んだポイントが \(N\) 個未満なら、\(J _ N\) までに距離が \(K\) 以上のポイントがあることがわかります。 よって、貪欲法は \(N\) 個以上のポイントを選び、\(L\ge N\) が示されました。

あとは、この貪欲法に従って判定を行えばよいです。 時間計算量は \(O(N)\) になります。

実装例は以下のようになります。

#include <iostream>
using namespace std;

int main() {
    int N, M;
    long K;
    cin >> N >> M >> K;
    // N = 1 なら、答えは 0
    if (N == 1) {
        cout << 0 << endl;
        return 0;
    }

    // 左端から右端までの距離
    long sum_D = 0;
    // 最後に選んだポイントからの距離
    long last_distance = 0;
    // 選んだポイントの個数
    int chosen = 1;
    for (int i = 0; i < M - 1; ++i) {
        int D;
        cin >> D;
        sum_D += D;
        last_distance += D;
        // 最後に選んだポイントから K 以上離れたら
        if (last_distance >= K) {
            ++chosen; // そのポイントを選ぶ
            last_distance = 0;
        }
    }

    // N 個以上ポイントを選ぶことができたかで場合分け
    if (chosen >= N) {
        cout << sum_D << endl;
    } else {
        cout << -1 << endl;
    }
    return 0;
}
N, M, K = map(int, input().split())

if N == 1:
    print(0)
    exit()

# 左端から右端までの距離
sum_D = 0
# 最後に選んだポイントからの距離
last_distance = 0
# 選んだポイントの個数
chosen = 1

for D in map(int, input().split()):
    sum_D += D
    last_distance += D
    
    # 最後に選んだポイントから K 以上離れたら
    if last_distance >= K:
        chosen += 1 # そのポイントを選ぶ
        last_distance = 0

# N 個以上ポイントを選ぶことができたかで場合分け
if chosen >= N:
    print(sum_D)
else:
    print(-1)

posted:
last update: