{ }codelper
累積和64ビット整数P0307

離れるほど冷めるたき火とキャンプ場の暖かさ

キャンプ場にテントの場所がn個一列に並んでいて、1番からn番まで番号がついている。
たき火をm個たく。
1つの場所にたき火がいくつあってもよい。
p番の場所に強さsでたいたたき火は、x番の場所をs − |x − p|だけ暖める。
この値が0以下なら、その場所はこのたき火では暖まらない。
つまりp番の場所はs、その両隣はs − 1、もう1つ離れるとs − 2というように、1つ離れるごとに1ずつ減る。
1番より左やn番より右には場所がないので、そちらへ出た暖かさは捨てられる。
1つの場所の暖かさは、すべてのたき火がその場所に与える値の合計である。
1行目にいちばん暖かい場所の暖かさとその番号を出力せよ。
そのような場所が複数あれば番号がいちばん小さいものを選ぶ。
続いてq個の質問に順に答えよ。
質問「l r」の答えは、l番からr番までの場所の暖かさの合計である。

入力
1行目にn、m、q、次のm行にたき火の「p s」、次のq行に質問の「l r」が空白1つで区切られて与えられる。
1 ≤ n ≤ 9,000、1 ≤ m ≤ 10,000、1 ≤ q ≤ 5,000、1 ≤ p ≤ n、1 ≤ s ≤ 1,000,000、1 ≤ l ≤ r ≤ n。
すべて整数
出力
1行目にいちばん暖かい場所の暖かさと番号を空白1つで区切って出力する。
次のq行に質問の答えを1行に1つずつ出力する。
カンマなしの数字だけで書く
例 1 · stdin
7 2 2
2 3
6 2
1 7
3 5
stdout
3 2
12
4
main.py
入力(stdin)

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

$ 準備完了

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

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

たき火ごとに届く場所を1つずつ足すとm × n = 9千万回で、PythonやCraykonには遅いです。
累積和を3重に使います。
たき火1つの暖かさは、左から1つごとに+1ずつ上がり、pを過ぎると−1ずつ下がる山の形なので、「傾き」は区間p−s+1 … pで+1、p+1 … p+sで−1です。
傾きへの区間加算は差分配列の両端2か所を直すだけで済み(P0259の方法)、全部集めてから1回累積すると傾き、もう1回で暖かさ、さらに1回で区間の合計を引き算1回で求める累積和になります。
例:暖かさは2 3 2 1 1 2 1で、3〜5番の合計は2 + 1 + 1 = 4です。
罠1:上り坂の始まりp − s + 1は1より小さくなりえます。
Pythonのdd[-3]はエラーなしで後ろから3番目を書きかえ、JavaScript・TypeScriptは配列に"-3"というプロパティを黙って作り、C・C++は配列の外のメモリを触り、Java・C#・Craykonは実行時エラーで止まります。
罠2:だからといって始まりを1番に寄せるだけでは間違いです — 1番の場所はすでにs − (p − 1)まで上がっているので、その高さを1番の傾きに別に足します。
右へはみ出た下り坂はn番より後ろを捨てるだけです。
罠3:累積を1回しかしないと「平らな区間加算」になり、山の形になりません。
罠4:区間の合計はprefix[r] − prefix[l − 1]です。
prefix[l]を引くとl番が抜けます。
罠5:いちばん暖かい場所を>=で置きかえると番号の大きい方が残ります。
罠6:1つの場所の暖かさは10^10、合計は約9×10^13まで行くので、C・C++はlong long、Java・C#はlongです(32ビットのintは約21億まで)。
Pythonの整数はあふれず、JavaScript・TypeScript・Craykonの数は2^53(約9×10^15)まで正確です。
Craykonは新リスト(n + 3, 0)で0の入ったリストを作り、数学.大きい方・数学.小さい方で範囲を切ります(Craykonのリストは10,000個まで入ります)。

Pythonmain.py · 44 行
import sys data = sys.stdin.buffer.read().split()n, m, q = int(data[0]), int(data[1]), int(data[2]) # dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)dd = [0] * (n + 3)pos = 3for _ in range(m):    p, s = int(data[pos]), int(data[pos + 1])    pos += 2    # 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다    first = s - (p - 1)    if first > 0:        dd[1] += first        dd[2] -= first    lo = max(2, p - s + 1)      # 오르막: lo..p에서 한 칸에 +1    if lo <= p:        dd[lo] += 1        dd[p + 1] -= 1    hi = min(n, p + s)          # 내리막: p+1..hi에서 한 칸에 -1    if p + 1 <= hi:        dd[p + 1] -= 1        dd[hi + 1] += 1 prefix = [0] * (n + 1)          # prefix[x] = 1..x번 자리의 따뜻함의 합slope = 0warm = 0best = -1best_at = 0for x in range(1, n + 1):    slope += dd[x]              # 첫 번째 누적: 기울기    warm += slope               # 두 번째 누적: 따뜻함    prefix[x] = prefix[x - 1] + warm   # 세 번째 누적: 구간 합을 위한 누적 합    if warm > best:        best = warm        best_at = x out = [f"{best} {best_at}"]for _ in range(q):    l, r = int(data[pos]), int(data[pos + 1])    pos += 2    out.append(str(prefix[r] - prefix[l - 1]))print("\n".join(out))
JavaScriptmain.js · 49 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m, q] = data; // dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)const dd = new Array(n + 3).fill(0);let pos = 3;for (let i = 0; i < m; i++) {  const p = data[pos++];  const s = data[pos++];  // 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다  const first = s - (p - 1);  if (first > 0) {    dd[1] += first;    dd[2] -= first;  }  const lo = Math.max(2, p - s + 1); // 오르막: lo..p에서 한 칸에 +1  if (lo <= p) {    dd[lo] += 1;    dd[p + 1] -= 1;  }  const hi = Math.min(n, p + s); // 내리막: p+1..hi에서 한 칸에 -1  if (p + 1 <= hi) {    dd[p + 1] -= 1;    dd[hi + 1] += 1;  }} const prefix = new Array(n + 1).fill(0); // prefix[x] = 1..x번 자리의 따뜻함의 합let slope = 0;let warm = 0;let best = -1;let bestAt = 0;for (let x = 1; x <= n; x++) {  slope += dd[x];  warm += slope;  prefix[x] = prefix[x - 1] + warm;  if (warm > best) {    best = warm;    bestAt = x;  }} const out = [best + " " + bestAt];for (let i = 0; i < q; i++) {  const l = data[pos++];  const r = data[pos++];  out.push(prefix[r] - prefix[l - 1]);}console.log(out.join("\n"));
TypeScriptmain.ts · 51 行
import { readFileSync } from "node:fs"; const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const [n, m, q] = data; // dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)const dd: number[] = new Array<number>(n + 3).fill(0);let pos = 3;for (let i = 0; i < m; i++) {  const p: number = data[pos++];  const s: number = data[pos++];  // 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다  const first: number = s - (p - 1);  if (first > 0) {    dd[1] += first;    dd[2] -= first;  }  const lo: number = Math.max(2, p - s + 1); // 오르막: lo..p에서 한 칸에 +1  if (lo <= p) {    dd[lo] += 1;    dd[p + 1] -= 1;  }  const hi: number = Math.min(n, p + s); // 내리막: p+1..hi에서 한 칸에 -1  if (p + 1 <= hi) {    dd[p + 1] -= 1;    dd[hi + 1] += 1;  }} const prefix: number[] = new Array<number>(n + 1).fill(0); // prefix[x] = 1..x번 자리의 따뜻함의 합let slope = 0;let warm = 0;let best = -1;let bestAt = 0;for (let x = 1; x <= n; x++) {  slope += dd[x];  warm += slope;  prefix[x] = prefix[x - 1] + warm;  if (warm > best) {    best = warm;    bestAt = x;  }} const out: string[] = [`${best} ${bestAt}`];for (let i = 0; i < q; i++) {  const l: number = data[pos++];  const r: number = data[pos++];  out.push(String(prefix[r] - prefix[l - 1]));}console.log(out.join("\n"));
JavaMain.java · 55 行
import java.io.*; public class Main {    public static void main(String[] args) throws IOException {        StreamTokenizer st = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));        st.nextToken(); int n = (int) st.nval;        st.nextToken(); int m = (int) st.nval;        st.nextToken(); int q = (int) st.nval;         // dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)        long[] dd = new long[n + 3];        for (int i = 0; i < m; i++) {            st.nextToken(); int p = (int) st.nval;            st.nextToken(); int s = (int) st.nval;            // 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다            int first = s - (p - 1);            if (first > 0) {                dd[1] += first;                dd[2] -= first;            }            int lo = Math.max(2, p - s + 1); // 오르막: lo..p에서 한 칸에 +1            if (lo <= p) {                dd[lo] += 1;                dd[p + 1] -= 1;            }            int hi = Math.min(n, p + s); // 내리막: p+1..hi에서 한 칸에 -1            if (p + 1 <= hi) {                dd[p + 1] -= 1;                dd[hi + 1] += 1;            }        }         long[] prefix = new long[n + 1]; // 10^14까지 가서 int로는 넘친다        long slope = 0, warm = 0, best = -1;        int bestAt = 0;        for (int x = 1; x <= n; x++) {            slope += dd[x];            warm += slope;            prefix[x] = prefix[x - 1] + warm;            if (warm > best) {                best = warm;                bestAt = x;            }        }         StringBuilder out = new StringBuilder();        out.append(best).append(' ').append(bestAt).append('\n');        for (int i = 0; i < q; i++) {            st.nextToken(); int l = (int) st.nval;            st.nextToken(); int r = (int) st.nval;            out.append(prefix[r] - prefix[l - 1]).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 64 行
using System;using System.Text; class Program{    static void Main()    {        var data = Console.In.ReadToEnd().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(data[0]), m = int.Parse(data[1]), q = int.Parse(data[2]);         // dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)        var dd = new long[n + 3];        int pos = 3;        for (int i = 0; i < m; i++)        {            int p = int.Parse(data[pos++]);            int s = int.Parse(data[pos++]);            // 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다            int first = s - (p - 1);            if (first > 0)            {                dd[1] += first;                dd[2] -= first;            }            int lo = Math.Max(2, p - s + 1); // 오르막: lo..p에서 한 칸에 +1            if (lo <= p)            {                dd[lo] += 1;                dd[p + 1] -= 1;            }            int hi = Math.Min(n, p + s); // 내리막: p+1..hi에서 한 칸에 -1            if (p + 1 <= hi)            {                dd[p + 1] -= 1;                dd[hi + 1] += 1;            }        }         var prefix = new long[n + 1]; // 10^14까지 가서 int로는 넘친다        long slope = 0, warm = 0, best = -1;        int bestAt = 0;        for (int x = 1; x <= n; x++)        {            slope += dd[x];            warm += slope;            prefix[x] = prefix[x - 1] + warm;            if (warm > best)            {                best = warm;                bestAt = x;            }        }         var sb = new StringBuilder();        sb.Append(best).Append(' ').Append(bestAt).Append('\n');        for (int i = 0; i < q; i++)        {            int l = int.Parse(data[pos++]);            int r = int.Parse(data[pos++]);            sb.Append(prefix[r] - prefix[l - 1]).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 55 行
#include <algorithm>#include <iostream>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, m, q;    cin >> n >> m >> q;     // dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)    vector<long long> dd(n + 3, 0);    for (int i = 0; i < m; i++) {        int p, s;        cin >> p >> s;        // 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다        int first = s - (p - 1);        if (first > 0) {            dd[1] += first;            dd[2] -= first;        }        int lo = max(2, p - s + 1); // 오르막: lo..p에서 한 칸에 +1        if (lo <= p) {            dd[lo] += 1;            dd[p + 1] -= 1;        }        int hi = min(n, p + s); // 내리막: p+1..hi에서 한 칸에 -1        if (p + 1 <= hi) {            dd[p + 1] -= 1;            dd[hi + 1] += 1;        }    }     vector<long long> prefix(n + 1, 0); // 10^14까지 가서 int로는 넘친다    long long slope = 0, warm = 0, best = -1;    int bestAt = 0;    for (int x = 1; x <= n; x++) {        slope += dd[x];        warm += slope;        prefix[x] = prefix[x - 1] + warm;        if (warm > best) {            best = warm;            bestAt = x;        }    }     cout << best << ' ' << bestAt << '\n';    for (int i = 0; i < q; i++) {        int l, r;        cin >> l >> r;        cout << prefix[r] - prefix[l - 1] << '\n';    }    return 0;}
Cmain.c · 51 行
#include <stdio.h> #define MAXN 9000 static long long dd[MAXN + 3];     /* dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지 */static long long prefix[MAXN + 1]; /* prefix[x] = 1..x번 자리의 따뜻함의 합(10^14까지) */ int main(void) {    int n, m, q;    if (scanf("%d %d %d", &n, &m, &q) != 3) return 1;    for (int i = 0; i < m; i++) {        int p, s;        scanf("%d %d", &p, &s);        /* 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다 */        int first = s - (p - 1);        if (first > 0) {            dd[1] += first;            dd[2] -= first;        }        int lo = p - s + 1 > 2 ? p - s + 1 : 2; /* 오르막: lo..p에서 한 칸에 +1 */        if (lo <= p) {            dd[lo] += 1;            dd[p + 1] -= 1;        }        int hi = p + s < n ? p + s : n; /* 내리막: p+1..hi에서 한 칸에 -1 */        if (p + 1 <= hi) {            dd[p + 1] -= 1;            dd[hi + 1] += 1;        }    }     long long slope = 0, warm = 0, best = -1;    int bestAt = 0;    for (int x = 1; x <= n; x++) {        slope += dd[x];        warm += slope;        prefix[x] = prefix[x - 1] + warm;        if (warm > best) {            best = warm;            bestAt = x;        }    }     printf("%lld %d\n", best, bestAt);    for (int i = 0; i < q; i++) {        int l, r;        scanf("%d %d", &l, &r);        printf("%lld\n", prefix[r] - prefix[l - 1]);    }    return 0;}
Craykonmain.cray · 56 行
첫줄 = コンソール.数読み();n = 첫줄[0];m = 첫줄[1];q = 첫줄[2]; // dd[x]: x번 자리에서 '기울기'가 얼마나 바뀌는지(두 번 누적하면 따뜻함이 된다)dd = 新リスト(n + 3, 0);繰り返す (i = 0; i < m; i++){    줄 = コンソール.数読み();    p = 줄[0];    s = 줄[1];    // 왼쪽 비탈이 1번 자리 밖에서 시작하면, 1번 자리는 이미 s - (p - 1)만큼 따뜻하다    first = s - (p - 1);    もし (first > 0)    {        dd[1] += first;        dd[2] -= first;    }    lo = 数学.大きい方(2, p - s + 1); // 오르막: lo..p에서 한 칸에 +1    もし (lo <= p)    {        dd[lo] += 1;        dd[p + 1] -= 1;    }    hi = 数学.小さい方(n, p + s); // 내리막: p+1..hi에서 한 칸에 -1    もし (p + 1 <= hi)    {        dd[p + 1] -= 1;        dd[hi + 1] += 1;    }} prefix = 新リスト(n + 1, 0); // prefix[x] = 1..x번 자리의 따뜻함의 합slope = 0;warm = 0;best = -1;bestAt = 0;繰り返す (x = 1; x <= n; x++){    slope += dd[x];    warm += slope;    prefix[x] = prefix[x - 1] + warm;    もし (warm > best)    {        best = warm;        bestAt = x;    }} コンソール.書く(best + " " + bestAt);繰り返す (i = 0; i < q; i++){    줄 = コンソール.数読み();    コンソール.書く(prefix[줄[1]] - prefix[줄[0] - 1]);}