{ }codelper
尺取り法キュー64ビット整数P0306

いちばんきつい区間はロープウェイで越える尾根道

尾根に沿って登山道の区間がn個一列につながっていて、1番からn番まで番号がついている。
i番の区間を歩くと体力をaᵢ使う。
登山者は連続する区間l番からr番まで(1 ≤ l ≤ r ≤ n)を選んで「道」とする。
道の中で体力をいちばん多く使う区間1つはロープウェイで通るので体力を使わない。
いちばん多く使う区間がいくつあっても、ロープウェイに乗るのは1区間だけである。
残りの区間で使う体力の合計がT以下なら、その道は「歩ける道」である。
区間が1つだけの道は使う体力が0なので、いつも歩ける道である。
1行目に歩ける道 (l, r) の個数を出力せよ。
2行目に歩ける道のうち区間数がいちばん多い道の区間数と開始番号lを出力せよ。
そのような道が複数あればlがいちばん小さいものを選ぶ。

入力
1行目にnとT、2行目にa₁ … aₙが空白1つで区切られて与えられる。
1 ≤ n ≤ 10,000、0 ≤ T ≤ 10,000,000,000,000(10^13)、1 ≤ aᵢ ≤ 1,000,000,000。
すべて整数
出力
2行:1行目に歩ける道の個数、2行目にいちばん長い道の区間数と開始番号を空白1つで区切って出力する。
カンマなしの数字だけで書く
例 1 · stdin
6 5
3 9 2 4 1 7
stdout
14
3 1
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

道を右に1区間のばしても「合計 − 最大値」は減りません(新しい区間が最大値より小さければその分、大きければ前の最大値の分だけ増えます)。
だから右端rを1つずつ進め、左端lはあふれたときだけ進める尺取り法で解け、rで終わる歩ける道はr − l + 1個です。
窓の最大値を毎回探し直すとn²なので、デックに「まだ最大値になりうる位置」だけを大きい順に残します:新しい区間以下の後ろの候補を取り除いてから入れ、左端が通りすぎた候補は前から取り除きます。
例:T = 5のとき9 2 4は9をロープウェイで越えて2 + 4 = 6なのでだめ、2 4 1は2 + 1 = 3なのでよいです。
罠1:合計だけを見て合計 ≤ Tで窓を動かす(ロープウェイを忘れる)と間違いです。
罠2:あふれたときifで1回だけ縮めると、最長の長さは合っても個数が違います — whileです。
罠3:左端が通りすぎた位置をデックの前から取り除かないと、もう窓を出た大きい区間を引き続けます。
罠4:今の最大値より小さい区間を候補から捨てると、最大値が窓を出たあと次の最大値がわかりません。
罠5:長さが同じとき>=で置きかえると開始が後ろの道が残ります。
罠6:合計は10^13まで行くので、C・C++はlong long、Java・C#はlongです(32ビットのintは約21億まで)。
Pythonの整数はあふれず、JavaScript・TypeScript・Craykonの数は2^53(約9×10^15)まで正確です。
デックは言語ごとに違います:Python collections.deque、Java ArrayDeque、C++ std::dequeがあり、C#にはデックのクラスがないので配列と先頭・末尾の番号で作ります(Cも同じ)。
JavaScript・TypeScriptの配列のshift()やCraykonのリストの位置で取り除く(0)は後ろを全部ずらすので、前から取り除くときは先頭の番号(head)を1つ進めるだけにします。

Pythonmain.py · 31 行
import sysfrom collections import deque data = sys.stdin.buffer.read().split()n, limit = int(data[0]), int(data[1])a = [int(x) for x in data[2:2 + n]] dq = deque()      # 창 안에서 '가장 힘든 구간' 후보의 자리. 힘이 큰 것부터 작은 것 순서로 남는다left = 0total = 0         # 창 안의 힘의 합count = 0best_len = 0best_start = 0for right in range(n):    total += a[right]    while dq and a[dq[-1]] <= a[right]:        dq.pop()    dq.append(right)    # 가장 힘든 구간(dq[0])은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다    while total - a[dq[0]] > limit:        total -= a[left]        if dq[0] == left:            dq.popleft()        left += 1    length = right - left + 1    count += length           # right에서 끝나는 길은 시작이 left..right인 것 모두    if length > best_len:     # 같으면 먼저 찾은(시작이 앞선) 것을 둔다        best_len = length        best_start = left + 1print(count)print(best_len, best_start)
JavaScriptmain.js · 32 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const limit = data[1];const a = data.slice(2, 2 + n); // 덱: 배열 하나와 머리 번호. shift()는 뒤의 것을 모두 당기니 쓰지 않는다const dq = new Array(n);let head = 0;let tail = 0; // dq[head..tail-1]이 창 안의 '가장 힘든 구간' 후보(힘이 큰 것부터)let left = 0;let total = 0;let count = 0;let bestLen = 0;let bestStart = 0;for (let right = 0; right < n; right++) {  total += a[right];  while (tail > head && a[dq[tail - 1]] <= a[right]) tail--;  dq[tail++] = right;  // 가장 힘든 구간(dq[head])은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다  while (total - a[dq[head]] > limit) {    total -= a[left];    if (dq[head] === left) head++;    left++;  }  const length = right - left + 1;  count += length;  if (length > bestLen) {    bestLen = length;    bestStart = left + 1;  }}console.log(count + "\n" + bestLen + " " + bestStart);
TypeScriptmain.ts · 34 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n: number = data[0];const limit: number = data[1];const a: number[] = data.slice(2, 2 + n); // 덱: 배열 하나와 머리 번호. shift()는 뒤의 것을 모두 당기니 쓰지 않는다const dq: number[] = new Array<number>(n).fill(0);let head = 0;let tail = 0; // dq[head..tail-1]이 창 안의 '가장 힘든 구간' 후보(힘이 큰 것부터)let left = 0;let total = 0;let count = 0;let bestLen = 0;let bestStart = 0;for (let right = 0; right < n; right++) {  total += a[right];  while (tail > head && a[dq[tail - 1]] <= a[right]) tail--;  dq[tail++] = right;  // 가장 힘든 구간(dq[head])은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다  while (total - a[dq[head]] > limit) {    total -= a[left];    if (dq[head] === left) head++;    left++;  }  const length: number = right - left + 1;  count += length;  if (length > bestLen) {    bestLen = length;    bestStart = left + 1;  }}console.log(`${count}\n${bestLen} ${bestStart}`);
JavaMain.java · 39 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(br.readLine());        int n = Integer.parseInt(st.nextToken());        long limit = Long.parseLong(st.nextToken());        long[] a = new long[n];        st = new StringTokenizer(br.readLine());        for (int i = 0; i < n; i++) a[i] = Long.parseLong(st.nextToken());         // 창 안에서 '가장 힘든 구간' 후보의 자리. 힘이 큰 것부터 작은 것 순서로 남는다        ArrayDeque<Integer> dq = new ArrayDeque<>();        int left = 0, bestLen = 0, bestStart = 0;        long total = 0; // 10^13까지 가서 int로는 넘친다        long count = 0;        for (int right = 0; right < n; right++) {            total += a[right];            while (!dq.isEmpty() && a[dq.peekLast()] <= a[right]) dq.pollLast();            dq.addLast(right);            // 가장 힘든 구간(맨 앞)은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다            while (total - a[dq.peekFirst()] > limit) {                total -= a[left];                if (dq.peekFirst() == left) dq.pollFirst();                left++;            }            int length = right - left + 1;            count += length;            if (length > bestLen) {                bestLen = length;                bestStart = left + 1;            }        }        System.out.println(count);        System.out.println(bestLen + " " + bestStart);    }}
C#Main.cs · 42 行
using System; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]);        long limit = long.Parse(data[1]);        var a = new long[n];        for (int i = 0; i < n; i++) a[i] = long.Parse(data[2 + i]);         // .NET에는 Deque 클래스가 없다: 배열 하나와 머리·꼬리 번호로 덱을 만든다        var dq = new int[n];        int head = 0, tail = 0; // dq[head..tail-1]이 창 안의 '가장 힘든 구간' 후보(힘이 큰 것부터)        int left = 0, bestLen = 0, bestStart = 0;        long total = 0; // 10^13까지 가서 int로는 넘친다        long count = 0;        for (int right = 0; right < n; right++)        {            total += a[right];            while (tail > head && a[dq[tail - 1]] <= a[right]) tail--;            dq[tail++] = right;            // 가장 힘든 구간(dq[head])은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다            while (total - a[dq[head]] > limit)            {                total -= a[left];                if (dq[head] == left) head++;                left++;            }            int length = right - left + 1;            count += length;            if (length > bestLen)            {                bestLen = length;                bestStart = left + 1;            }        }        Console.WriteLine(count);        Console.WriteLine($"{bestLen} {bestStart}");    }}
C++main.cpp · 38 行
#include <deque>#include <iostream>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n;    long long limit;    cin >> n >> limit;    vector<long long> a(n);    for (auto &x : a) cin >> x;     deque<int> dq; // 창 안에서 '가장 힘든 구간' 후보의 자리. 힘이 큰 것부터 작은 것 순서로 남는다    int left = 0, bestLen = 0, bestStart = 0;    long long total = 0; // 10^13까지 가서 int로는 넘친다    long long count = 0;    for (int right = 0; right < n; right++) {        total += a[right];        while (!dq.empty() && a[dq.back()] <= a[right]) dq.pop_back();        dq.push_back(right);        // 가장 힘든 구간(dq.front())은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다        while (total - a[dq.front()] > limit) {            total -= a[left];            if (dq.front() == left) dq.pop_front();            left++;        }        int length = right - left + 1;        count += length;        if (length > bestLen) {            bestLen = length;            bestStart = left + 1;        }    }    cout << count << "\n" << bestLen << " " << bestStart << "\n";    return 0;}
Cmain.c · 36 行
#include <stdio.h> #define MAXN 10000 static long long a[MAXN];static int dq[MAXN]; /* 덱: dq[head..tail-1]이 창 안의 '가장 힘든 구간' 후보(힘이 큰 것부터) */ int main(void) {    int n;    long long limit;    if (scanf("%d %lld", &n, &limit) != 2) return 1;    for (int i = 0; i < n; i++) scanf("%lld", &a[i]);     int head = 0, tail = 0, left = 0, bestLen = 0, bestStart = 0;    long long total = 0; /* 10^13까지 가서 int로는 넘친다 */    long long count = 0;    for (int right = 0; right < n; right++) {        total += a[right];        while (tail > head && a[dq[tail - 1]] <= a[right]) tail--;        dq[tail++] = right;        /* 가장 힘든 구간(dq[head])은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다 */        while (total - a[dq[head]] > limit) {            total -= a[left];            if (dq[head] == left) head++;            left++;        }        int length = right - left + 1;        count += length;        if (length > bestLen) {            bestLen = length;            bestStart = left + 1;        }    }    printf("%lld\n%d %d\n", count, bestLen, bestStart);    return 0;}
Craykonmain.cray · 41 行
첫줄 = コンソール.数読み();n = 첫줄[0];limit = 첫줄[1];a = コンソール.数読み(); // 덱: 목록 하나와 머리 번호. 자리빼기(0)은 뒤의 것을 모두 당기니 머리 번호만 옮긴다dq = [];head = 0; // dq[head..]가 창 안의 '가장 힘든 구간' 후보(힘이 큰 것부터)left = 0;total = 0;count = 0;bestLen = 0;bestStart = 0;繰り返す (right = 0; right < n; right++){    total += a[right];    間 (dq.個数 > head && a[dq[dq.個数 - 1]] <= a[right])    {        dq.位置で取り除く(dq.個数 - 1);    }    dq.追加(right);    // 가장 힘든 구간(dq[head])은 케이블카로 지나가니 뺀다. 넘치면 왼쪽을 줄인다    間 (total - a[dq[head]] > limit)    {        total -= a[left];        もし (dq[head] == left)        {            head += 1;        }        left += 1;    }    length = right - left + 1;    count += length;    もし (length > bestLen)    {        bestLen = length;        bestStart = left + 1;    }}コンソール.書く(count);コンソール.書く(bestLen + " " + bestStart);