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

円卓に料理を出す

円卓に n 人の客が座っている。
席には時計回りに 1 番、2 番、…、n 番が付いていて、n 番の席の時計回りの隣は 1 番の席である。
i 番の席の客の料理を出すには t_i 秒かかる。
店員は s 番の席から始めて一方向に回りながら、続けて座っている k 人に順に料理を出す。
客が待った時間は、自分より先に受け取った客たちの料理を出すのにかかった時間をすべて足した値である。
自分の料理を出す時間は入れない。
q 個の質問が与えられる。
質問 "s k" ごとに、時計回りに回るときに k 人が待った時間の合計と、反時計回りに回るときに k 人が待った時間の合計を、空白一つで区切って一行に出力せよ。
例 1 の最初の質問 "1 3" では、時計回りに回ると 1 番、2 番、3 番の席の客が順に 0 秒、3 秒、4 秒待ち、合計は 7 である。
反時計回りに回ると 1 番、5 番、4 番の席の客が順に 0 秒、3 秒、8 秒待ち、合計は 11 である。

入力
一行目に n と q が空白一つで区切って与えられる。
二行目に t_1, …, t_n が空白一つで区切って与えられる。
続く q 行に s と k が空白一つで区切って与えられる。
1 ≤ n ≤ 9,000、1 ≤ q ≤ 7,000、1 ≤ t_i ≤ 10^8、1 ≤ s ≤ n、1 ≤ k ≤ n(すべて整数)
出力
質問ごとに一行で、時計回りに回るときの合計と反時計回りに回るときの合計
例 1 · stdin
5 4
3 1 4 1 5
1 3
4 4
2 1
3 5
stdout
7 11
16 12
0 0
32 30
例 2 · stdin
8 2
100000000 100000000 100000000 100000000 100000000 100000000 100000000 100000000
1 8
6 5
stdout
2800000000 2800000000
1000000000 1000000000
main.py
入力(stdin)

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

$ 準備完了

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

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

j 番目(0 から数えて)に受け取る客の料理の時間は、その後に受け取る k-1-j 人がみな待ちます。
だから答えは t × (k-1-j) をすべて足した値で、掛ける数は席を一つ移るごとに 1 ずつ減ります。
このような和は累積和二つで求めます。
P[i] は前から i 個の t を足した値、Q[i] は 席の番号 × t を足した値です(席の番号は 0 から)。
席 a から b-1 までを番号が大きくなる向きに回り、a の掛ける数が c なら、和は c × (P[b] - P[a]) - ((Q[b] - Q[a]) - a × (P[b] - P[a])) です。
番号が小さくなる向きに回るときは Q を逆に使います。
円卓なので、n 番の席を越えると二つに分けます。
二つ目の区間の最初の客の掛ける数は、一つ目の区間の人数だけ減った値です。
小さな計算二つを、Python は関数の中の関数で、C++ はラムダで、Java・C# は static メソッドで、C・JavaScript・TypeScript・Craykon はふつうの関数でまとめました。
累積和のリストは Python [0] * (n + 1)、JavaScript new Array(n + 1).fill(0)、Craykon 新リスト(n + 1, 0) のように 0 で埋めて作ります。
罠その一:質問ごとに k 人を一人ずつ回って足すと、最も大きい入力で約 1 億 2 千万回足します。
こう書いた Python の解答は時間内に終わらず、Craykon も時間がかかりすぎると言って止まりました。
正直に言うと、同じやり方の C の解答はこのコンピュータで 0.3 秒以内に終わりました。
累積和がどうしても必要なのは遅い言語でのことです。
罠その二:二つ目の区間で掛ける数をまた k-1 から始めてはいけません。
例 1 の二番目の質問 "4 4" は時計回りの合計が 16 なのに 24 が出て、最初の質問の反時計回りの合計は 11 なのに 17 が出ます。
罠その三:自分の料理を出す時間まで入れると(掛ける数を k から始めると)、例 1 の最初の質問が 7 11 ではなく 15 20 になり、一人だけの三番目の質問も 0 0 ではなく 1 1 になります。
罠その四:反時計回りに回る k 人は、ある席から時計回りに回る k 人と同じ人たちですが、順番が逆なので合計が違います。
例 1 の最初の質問で反時計回り(1 番、5 番、4 番)を 4 番から始める時計回り(4 番、5 番、1 番)に変えて求めると、11 ではなく 7 が出ます。
罠その五:答えは約 4 × 10^15 まで大きくなります。
Java・C#・C・C++ は累積和と答えを long/long long に入れなければなりません。
Java ですべて int に入れると、例 2 の最初の質問が 2800000000 ではなく -1494967296 と出ます。
Python の整数には大きさの制限がありません。
JavaScript・TypeScript・Craykon の数は 2^53(約 9.007 × 10^15)までしか正確ではありませんが、この問題は途中に出てくる積も約 8.1 × 10^15 を超えないので、そのままで合います。

Pythonmain.py · 42 行
import sys  def main():    data = sys.stdin.buffer.read().split()    n, q = int(data[0]), int(data[1])    t = [int(x) for x in data[2:2 + n]]    # P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합    P = [0] * (n + 1)    Q = [0] * (n + 1)    for i in range(n):        P[i + 1] = P[i] + t[i]        Q[i + 1] = Q[i] + i * t[i]     def forward(a, b, c):        """자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다."""        dp = P[b] - P[a]        return c * dp - ((Q[b] - Q[a]) - a * dp)     def backward(a, b, c):        """자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다."""        dp = P[b] - P[a]        return c * dp - ((b - 1) * dp - (Q[b] - Q[a]))     out = []    pos = 2 + n    for _ in range(q):        s, k = int(data[pos]) - 1, int(data[pos + 1])        pos += 2        if s + k <= n:            cw = forward(s, s + k, k - 1)        else:            cw = forward(s, n, k - 1) + forward(0, s + k - n, k - 1 - (n - s))        if k <= s + 1:            ccw = backward(s - k + 1, s + 1, k - 1)        else:            ccw = backward(0, s + 1, k - 1) + backward(n - (k - s - 1), n, k - 1 - (s + 1))        out.append("%d %d" % (cw, ccw))    print("\n".join(out))  main()
JavaScriptmain.js · 36 行
const data = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const q = data[1];// P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합const P = new Array(n + 1).fill(0);const Q = new Array(n + 1).fill(0);for (let i = 0; i < n; i++) {  P[i + 1] = P[i] + data[2 + i];  Q[i + 1] = Q[i] + i * data[2 + i];} // 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다function forward(a, b, c) {  const dp = P[b] - P[a];  return c * dp - (Q[b] - Q[a] - a * dp);} // 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다function backward(a, b, c) {  const dp = P[b] - P[a];  return c * dp - ((b - 1) * dp - (Q[b] - Q[a]));} const out = [];for (let i = 0; i < q; i++) {  const s = data[2 + n + 2 * i] - 1;  const k = data[3 + n + 2 * i];  let cw;  if (s + k <= n) cw = forward(s, s + k, k - 1);  else cw = forward(s, n, k - 1) + forward(0, s + k - n, k - 1 - (n - s));  let ccw;  if (k <= s + 1) ccw = backward(s - k + 1, s + 1, k - 1);  else ccw = backward(0, s + 1, k - 1) + backward(n - (k - s - 1), n, k - 1 - (s + 1));  out.push(cw + " " + ccw);}console.log(out.join("\n"));
TypeScriptmain.ts · 37 行
import { readFileSync } from "node:fs";const data: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);const n = data[0];const q = data[1];// P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합const P: number[] = new Array<number>(n + 1).fill(0);const Q: number[] = new Array<number>(n + 1).fill(0);for (let i = 0; i < n; i++) {  P[i + 1] = P[i] + data[2 + i];  Q[i + 1] = Q[i] + i * data[2 + i];} // 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다function forward(a: number, b: number, c: number): number {  const dp = P[b] - P[a];  return c * dp - (Q[b] - Q[a] - a * dp);} // 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다function backward(a: number, b: number, c: number): number {  const dp = P[b] - P[a];  return c * dp - ((b - 1) * dp - (Q[b] - Q[a]));} const out: string[] = [];for (let i = 0; i < q; i++) {  const s = data[2 + n + 2 * i] - 1;  const k = data[3 + n + 2 * i];  let cw: number;  if (s + k <= n) cw = forward(s, s + k, k - 1);  else cw = forward(s, n, k - 1) + forward(0, s + k - n, k - 1 - (n - s));  let ccw: number;  if (k <= s + 1) ccw = backward(s - k + 1, s + 1, k - 1);  else ccw = backward(0, s + 1, k - 1) + backward(n - (k - s - 1), n, k - 1 - (s + 1));  out.push(cw + " " + ccw);}console.log(out.join("\n"));
JavaMain.java · 46 行
import java.io.*;import java.util.*; public class Main {    static long[] P, Q; // P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합     // 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다    static long forward(int a, int b, int c) {        long dp = P[b] - P[a];        return c * dp - ((Q[b] - Q[a]) - a * dp);    }     // 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다    static long backward(int a, int b, int c) {        long dp = P[b] - P[a];        return c * dp - ((b - 1) * dp - (Q[b] - Q[a]));    }     public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        StringTokenizer st = new StringTokenizer(in.readLine());        int n = Integer.parseInt(st.nextToken());        int q = Integer.parseInt(st.nextToken());        P = new long[n + 1];        Q = new long[n + 1];        st = new StringTokenizer(in.readLine());        for (int i = 0; i < n; i++) {            long t = Long.parseLong(st.nextToken());            P[i + 1] = P[i] + t;            Q[i + 1] = Q[i] + i * t;        }        StringBuilder out = new StringBuilder();        for (int i = 0; i < q; i++) {            st = new StringTokenizer(in.readLine());            int s = Integer.parseInt(st.nextToken()) - 1;            int k = Integer.parseInt(st.nextToken());            long cw, ccw;            if (s + k <= n) cw = forward(s, s + k, k - 1);            else cw = forward(s, n, k - 1) + forward(0, s + k - n, k - 1 - (n - s));            if (k <= s + 1) ccw = backward(s - k + 1, s + 1, k - 1);            else ccw = backward(0, s + 1, k - 1) + backward(n - (k - s - 1), n, k - 1 - (s + 1));            out.append(cw).append(' ').append(ccw).append('\n');        }        System.out.print(out);    }}
C#Main.cs · 48 行
using System;using System.Text; class Program{    static long[] P, Q; // P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합     // 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다    static long Forward(int a, int b, int c)    {        long dp = P[b] - P[a];        return c * dp - ((Q[b] - Q[a]) - a * dp);    }     // 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다    static long Backward(int a, int b, int c)    {        long dp = P[b] - P[a];        return c * dp - ((b - 1) * dp - (Q[b] - Q[a]));    }     static void Main()    {        string[] p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);        int n = int.Parse(p[0]), q = int.Parse(p[1]);        long[] t = Array.ConvertAll(Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries), long.Parse);        P = new long[n + 1];        Q = new long[n + 1];        for (int i = 0; i < n; i++)        {            P[i + 1] = P[i] + t[i];            Q[i + 1] = Q[i] + i * t[i];        }        var sb = new StringBuilder();        for (int i = 0; i < q; i++)        {            p = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);            int s = int.Parse(p[0]) - 1, k = int.Parse(p[1]);            long cw, ccw;            if (s + k <= n) cw = Forward(s, s + k, k - 1);            else cw = Forward(s, n, k - 1) + Forward(0, s + k - n, k - 1 - (n - s));            if (k <= s + 1) ccw = Backward(s - k + 1, s + 1, k - 1);            else ccw = Backward(0, s + 1, k - 1) + Backward(n - (k - s - 1), n, k - 1 - (s + 1));            sb.Append(cw).Append(' ').Append(ccw).Append('\n');        }        Console.Write(sb);    }}
C++main.cpp · 40 行
#include <iostream>#include <vector>using namespace std; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, q;    cin >> n >> q;    // P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합    vector<long long> P(n + 1, 0), Q(n + 1, 0);    for (int i = 0; i < n; i++) {        long long t;        cin >> t;        P[i + 1] = P[i] + t;        Q[i + 1] = Q[i] + i * t;    }    // 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다    auto forward = [&](int a, int b, int c) -> long long {        long long dp = P[b] - P[a];        return c * dp - ((Q[b] - Q[a]) - a * dp);    };    // 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다    auto backward = [&](int a, int b, int c) -> long long {        long long dp = P[b] - P[a];        return c * dp - ((b - 1) * dp - (Q[b] - Q[a]));    };    while (q--) {        int s, k;        cin >> s >> k;        s--;        long long cw, ccw;        if (s + k <= n) cw = forward(s, s + k, k - 1);        else cw = forward(s, n, k - 1) + forward(0, s + k - n, k - 1 - (n - s));        if (k <= s + 1) ccw = backward(s - k + 1, s + 1, k - 1);        else ccw = backward(0, s + 1, k - 1) + backward(n - (k - s - 1), n, k - 1 - (s + 1));        cout << cw << ' ' << ccw << '\n';    }    return 0;}
Cmain.c · 39 行
#include <stdio.h> /* P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합 */static long long P[9001], Q[9001]; /* 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다 */static long long forward(int a, int b, int c) {    long long dp = P[b] - P[a];    return c * dp - ((Q[b] - Q[a]) - a * dp);} /* 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다 */static long long backward(int a, int b, int c) {    long long dp = P[b] - P[a];    return c * dp - ((b - 1) * dp - (Q[b] - Q[a]));} int main(void) {    int n, q;    if (scanf("%d %d", &n, &q) != 2) return 0;    for (int i = 0; i < n; i++) {        long long t;        if (scanf("%lld", &t) != 1) return 0;        P[i + 1] = P[i] + t;        Q[i + 1] = Q[i] + i * t;    }    for (int i = 0; i < q; i++) {        int s, k;        if (scanf("%d %d", &s, &k) != 2) return 0;        s--;        long long cw, ccw;        if (s + k <= n) cw = forward(s, s + k, k - 1);        else cw = forward(s, n, k - 1) + forward(0, s + k - n, k - 1 - (n - s));        if (k <= s + 1) ccw = backward(s - k + 1, s + 1, k - 1);        else ccw = backward(0, s + 1, k - 1) + backward(n - (k - s - 1), n, k - 1 - (s + 1));        printf("%lld %lld\n", cw, ccw);    }    return 0;}
Craykonmain.cray · 52 行
첫줄 = コンソール.数読み();n = 첫줄[0];q = 첫줄[1];t = コンソール.数読み();// P[i] = t[0..i-1]의 합, Q[i] = j * t[j] (j < i)의 합P = 新リスト(n + 1, 0);Q = 新リスト(n + 1, 0);繰り返す (i = 0; i < n; i++){    P[i + 1] = P[i] + t[i];    Q[i + 1] = Q[i] + i * t[i];} // 자리 a..b-1을 번호가 커지는 쪽으로 돌 때: a가 c번, 그다음이 c-1번, … 기다리게 한다앞으로(a, b, c){    dp = P[b] - P[a];    返す c * dp - (Q[b] - Q[a] - a * dp);} // 자리 b-1..a를 번호가 작아지는 쪽으로 돌 때: b-1이 c번, 그다음이 c-1번, … 기다리게 한다뒤로(a, b, c){    dp = P[b] - P[a];    返す c * dp - ((b - 1) * dp - (Q[b] - Q[a]));} 繰り返す (i = 0; i < q; i++){    줄 = コンソール.数読み();    s = 줄[0] - 1;    k = 줄[1];    시계 = 0;    반시계 = 0;    もし (s + k <= n)    {        시계 = 앞으로(s, s + k, k - 1);    }    でなければ    {        시계 = 앞으로(s, n, k - 1) + 앞으로(0, s + k - n, k - 1 - (n - s));    }    もし (k <= s + 1)    {        반시계 = 뒤로(s - k + 1, s + 1, k - 1);    }    でなければ    {        반시계 = 뒤로(0, s + 1, k - 1) + 뒤로(n - (k - s - 1), n, k - 1 - (s + 1));    }    コンソール.書く(시계 + " " + 반시계);}