{ }codelper
ポインタP0246

ポインタ配列で作る箱の順位表

1 番から n 番までの箱が n 個あり、i 番の箱の重さは w_i である。
順位表は n 枠で、枠ごとに箱を一つ指している。
最初は r 番目の枠が r 番の箱を指している。
命令は五種類である。
- sort:順位表を作り直す。
重い箱を指す枠が前に来て、重さが同じなら番号の小さい箱が前に来る。
- add r v:順位表の r 番目の枠が指す箱の重さに v を足す。
順位表は作り直さない。
- swap i j:i 番の箱と j 番の箱の重さを入れ替える。
順位表の枠は引き続き同じ番号の箱を指す。
i と j は同じこともあり、そのときは何も変わらない。
- who r:順位表の r 番目の枠が指す箱の番号を一行に出力する。
- show:順位表の最初の枠から順に、枠が指す箱の重さ n 個を空白一つで区切って一行に出力する。
一行目に n と q、二行目に w_1 から w_n まで、続く q 行に命令が一つずつ与えられる。
重さは配列 int w[n] に置き、順位表はその要素を指すポインタ配列 int *rank[n] で作ってみよう。
sort では重さを動かさず、qsort(C++ は std::sort)でポインタだけを並べる。
重さが同じときは二つのポインタそのものを比べ、who はポインタの引き算 rank[r - 1] - w で番号を求めてみよう。
swap は二つの要素のアドレスを受け取る関数 void swap_weight(int *a, int *b) にしてみよう。

入力
1 ≤ n ≤ 1,000、1 ≤ q ≤ 2,000、1 ≤ r, i, j ≤ n。
重さと v は整数で、最初も add のあとも重さは常に -2,000,000,000 以上 2,000,000,000 以下(|v| もこの範囲内)。
show が出力する数は全部合わせて 15,000 個以下。
who か show は一回以上出てくる。
出力
who と show ごとに一行
例 1 · stdin
4 10
5 -2 5 7
sort
show
who 2
add 4 10
swap 1 2
show
sort
show
who 3
swap 3 3
stdout
7 5 5 -2
1
7 8 5 5
8 7 5 5
2
main.py
入力(stdin)

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

$ 準備完了

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

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

この問題は C・C++ だけを受け付けます。
アドレスを値として持つポインタ、ポインタどうしの引き算で何枠離れているかを求めること、アドレスの大小比較はこの二つの言語にしかありません。
Python・JavaScript・TypeScript・Java・C#・Craykon では順位表に箱の番号(位置)を入れて w[rank[r]] でたどるのが同じことです(C# の本物のポインタは unsafe の中だけ)。
C の qsort は並べる枠のアドレスを const void * として比較関数に渡します。
枠に入っているのは int * なので、*(int *const *)pa で一回ほどくとポインタ、もう一回ほどくと(*a)重さが出てきます。
C++ の std::sort は比較関数に const int * を二つ直接渡します。
同じ配列の中を指す二つのポインタは < で比べられ(アドレスが小さければ番号も小さい)、引き算するとその間の枠の数になります(rank[r - 1] - w + 1 が番号)。
落とし穴は五つ:① 比較関数で *(const int *)pa のように一回しかほどかないと、重さではなくポインタの値の一部を比べてしまい、順番がめちゃくちゃになります。
② return *b - *a; は 20億 - (-20億) のように差が int の範囲(約 21 億)を超えると符号が反転します。
大小を比べて -1・1 を返してください。
③ 一時変数なしで *a ^= *b; *b ^= *a; *a ^= *b; と入れ替えると、a と b が同じ要素のとき(swap 3 3)重さが 0 になります。
二つのポインタが同じ場所を指しうること(エイリアス)をいつも考えましょう。
④ 順位表にポインタではなく重さのコピーを置くと、add はコピーだけを変え、swap は順位表に表れません。
⑤ 同じ重さを番号で分けずに C++ の std::stable_sort に任せると、元の番号順ではなく add・swap を経た今の順位表の順番が残ります。

C++main.cpp · 51 行
#include <algorithm>#include <iostream>#include <string>#include <utility>#include <vector> // a와 b가 같은 칸을 가리켜도 맞게 바꾼다void swap_weight(int *a, int *b) {    int tmp = *a;    *a = *b;    *b = tmp;} int main() {    std::ios::sync_with_stdio(false);    std::cin.tie(nullptr);    int n, q;    std::cin >> n >> q;    std::vector<int> w(n);    // 상자의 무게. 크기를 다시 바꾸지 않으니 포인터가 계속 맞다    std::vector<int *> rank(n); // 순위표: 칸마다 w의 한 칸을 가리킨다    for (int i = 0; i < n; i++) {        std::cin >> w[i];        rank[i] = &w[i];    }    for (int k = 0; k < q; k++) {        std::string cmd;        std::cin >> cmd;        if (cmd == "sort") {            std::sort(rank.begin(), rank.end(), [](const int *a, const int *b) {                if (*a != *b) return *a > *b;                return a < b; // 무게가 같으면 주소(= 번호)가 작은 쪽이 앞            });        } else if (cmd == "add") {            int r, v;            std::cin >> r >> v;            *rank[r - 1] += v;        } else if (cmd == "swap") {            int i, j;            std::cin >> i >> j;            swap_weight(&w[i - 1], &w[j - 1]);        } else if (cmd == "who") {            int r;            std::cin >> r;            std::cout << (rank[r - 1] - w.data()) + 1 << '\n';        } else {            for (int i = 0; i < n; i++) std::cout << (i ? " " : "") << *rank[i];            std::cout << '\n';        }    }    return 0;}
Cmain.c · 53 行
#include <stdio.h>#include <stdlib.h>#include <string.h> static int w[1000];     /* 상자의 무게: w[0]이 1번 상자 */static int *rank[1000]; /* 순위표: 칸마다 w의 한 칸을 가리킨다 */ /* qsort는 늘어놓을 칸의 주소를 const void *로 준다. 칸에 든 것이 int *이니 진짜 타입은 int *const * */static int heavier_first(const void *pa, const void *pb) {    const int *a = *(int *const *)pa;    const int *b = *(int *const *)pb;    if (*a != *b) return *a > *b ? -1 : 1; /* 빼기(*b - *a)는 int를 넘칠 수 있다 */    return (a > b) - (a < b);              /* 같은 배열 안의 포인터는 크기를 비교할 수 있다: 주소가 작으면 번호도 작다 */} /* a와 b가 같은 칸을 가리켜도 맞게 바꾼다(임시 변수) */static void swap_weight(int *a, int *b) {    int tmp = *a;    *a = *b;    *b = tmp;} int main(void) {    int n, q;    char cmd[8];    scanf("%d %d", &n, &q);    for (int i = 0; i < n; i++) {        scanf("%d", &w[i]);        rank[i] = &w[i];    }    for (int k = 0; k < q; k++) {        scanf("%7s", cmd);        if (strcmp(cmd, "sort") == 0) {            qsort(rank, n, sizeof rank[0], heavier_first);        } else if (strcmp(cmd, "add") == 0) {            int r, v;            scanf("%d %d", &r, &v);            *rank[r - 1] += v; /* 가리키는 상자의 무게가 바뀐다 */        } else if (strcmp(cmd, "swap") == 0) {            int i, j;            scanf("%d %d", &i, &j);            swap_weight(&w[i - 1], &w[j - 1]);        } else if (strcmp(cmd, "who") == 0) {            int r;            scanf("%d", &r);            printf("%d\n", (int)(rank[r - 1] - w) + 1); /* 포인터 빼기 = 몇 칸 떨어져 있나 */        } else {            for (int i = 0; i < n; i++) printf(i ? " %d" : "%d", *rank[i]);            printf("\n");        }    }    return 0;}