{ }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++ 中写成 lambda,在 Java、C# 中写成 static 方法,在 C、JavaScript、TypeScript、Craykon 中写成普通函数。
前缀和列表像 Python [0] * (n + 1)、JavaScript new Array(n + 1).fill(0)、Craykon 新列表(n + 1, 0) 这样用 0 填满来创建。
陷阱一:每个询问都一个人一个人地转着加,在最大的输入上要加约 1.2 亿次。
这样写的 Python 解答没能在时限内跑完,Craykon 也说花的时间太长而停下了。
说实话,同样写法的 C 解答在这台电脑上 0.3 秒内就跑完了。
前缀和只有在慢的语言里才是非用不可的。
陷阱二:第二段的乘数不能又从 k-1 开始。
示例 1 的第二个询问 "4 4" 顺时针的和是 16,却得到 24;第一个询问逆时针的和是 11,却得到 17。
陷阱三:把给自己上菜的时间也算进去(乘数从 k 开始),示例 1 的第一个询问就变成 15 20 而不是 7 11,只有一个人的第三个询问也变成 1 1 而不是 0 0。
陷阱四:逆时针转到的 k 个人和从某个座位顺时针转到的 k 个人是同一批人,但顺序相反,所以和不同。
把示例 1 第一个询问的逆时针(1 号、5 号、4 号)换成从 4 号开始的顺时针(4 号、5 号、1 号)来算,得到 7 而不是 11。
陷阱五:答案会大到约 4 × 10^15。
Java、C#、C、C++ 必须把前缀和与答案放在 long/long long 里。
在 Java 中全用 int,示例 2 的第一个询问会输出 -1494967296 而不是 2800000000。
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));    }    控制台.输出(시계 + " " + 반시계);}