{ }codelper
標準コンテナキューP0207

車両基地で列車をつなぐ

車両基地で車両を一列につないで列車を作る。
最初は車両が一つもない。
命令 n 個を順に処理せよ。
列の前と後ろの両方でつないだり外したりするので、標準ライブラリの両端キュー(デック)コンテナを使う。
- addf x:番号 x の車両をいちばん前につなぐ。
- addb x:番号 x の車両をいちばん後ろにつなぐ。
- cutf k:いちばん前の車両を外すことを k 回行う。
残った車両が k 両より少なければ、ある車両をすべて外して止める。
外した車両の番号を外した順に(いちばん前の車両から)一行に空白一つで区切って出力する。
外した車両がなければ - を出力する。
- cutb k:いちばん後ろの車両を外すことを k 回行う。
ほかの決まりは cutf と同じで、外した順なのでいちばん後ろの車両から出力する。
- rot k:いちばん前の車両を外していちばん後ろにつなぐことを k 回行う。
何も出力しない。
車両がなければ何もしない。
すべての命令が終わったら残った車両の数を出力し、次の行に残った車両の番号を前から空白一つで区切って出力する。
残った車両がなければ二行目に - を出力する。

入力
最初の行に n(1 ≤ n ≤ 5000)。
続く n 行に命令が一つずつ。
addf・addb の 1 ≤ x ≤ 10^9、cutf・cutb の 1 ≤ k ≤ 10^9、rot の 0 ≤ k ≤ 10^9。
車両の番号は重なることもある。
出力
cutf・cutb ごとに一行(外した車両の番号、なければ -)、その次に残った車両の数、その次に残った車両の番号(なければ -)
例 1 · stdin
9
addb 1
addb 2
addf 3
rot 1
cutb 2
addb 9
addf 4
rot 4
cutf 1
stdout
3 2
1
2
9 4
例 2 · stdin
5
cutf 3
addf 7
addf 8
cutb 5
rot 2
stdout
-
7 8
0
-
main.py
入力(stdin)

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

$ 準備完了

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

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

この問題は Python・Java・C#・C++ だけを受け付けます。
前後両側で O(1) で入れたり外したりできるコンテナが標準ライブラリにある言語です。
JavaScript・TypeScript の配列の unshift・shift は前側で出し入れするときにすべての要素を動かすことがあり、C にはコンテナがなく、Craykon のリストの 挿入(0, x)・位置で取り除く(0) も後ろの要素を一つずつずらします。
名前は言語ごとに違います。
Python collections.deque の appendleft・append・popleft・pop・rotate、Java ArrayDeque の addFirst・addLast・pollFirst・pollLast、C++ std::deque の push_front・push_back・front+pop_front(pop_front() は値を返しません)・std::rotate です。
C# には Deque がないので、LinkedList<T> の AddFirst・AddLast・RemoveFirst・RemoveLast を使います。
外す回数はまず min(k, 残りの数) に減らし、回すのは k を車両数で割った余りの分だけで十分です(一周すると元どおり)。
Python の rotate は自動で減らしてくれます。
落とし穴は五つ:① Python rotate(k) は右に回り、後ろのものが前に来ます。
前のものを後ろへ送るには rotate(-k) です。
C++ std::rotate(begin, begin + r, end) も真ん中の値が「先頭になる要素」なので、end - r を渡すと逆に回ります。
② Python deque.pop() は後ろから外します。
前から外すのは popleft() です。
③ Java ArrayDeque.push はスタックのように前(addFirst)に入れます。
後ろにつなぐのは addLast(add・offer)です。
④ 減らさずに k 回外すと、空のコンテナから外すことになります。
C# は train.First が null なので NullReferenceException で止まり、Java pop・removeFirst は NoSuchElementException を投げ、pollFirst は null を返すので int に入れた瞬間に NullPointerException になります。
Python popleft は IndexError を出し、C++ front() は未定義動作です。
⑤ cutb はいちばん後ろの車両から外すので、外した順に書くと列車につながっていた順と逆になります。

Pythonmain.py · 27 行
import sysfrom collections import deque lines = sys.stdin.read().split("\n")n = int(lines[0])train = deque()  # 앞뒤 어느 쪽에서도 넣고 빼는 데 O(1)out = []for cmd in lines[1:n + 1]:    op, arg = cmd.split()    k = int(arg)    if op == "addf":        train.appendleft(k)    elif op == "addb":        train.append(k)    elif op in ("cutf", "cutb"):        # 남은 차량보다 많이 떼라고 하면 있는 만큼만 뗀다(빈 deque에서 빼면 IndexError)        take = min(k, len(train))        if op == "cutf":            cut = [train.popleft() for _ in range(take)]        else:            cut = [train.pop() for _ in range(take)]  # 뗀 차례: 맨 뒤 차량부터        out.append(" ".join(map(str, cut)) if cut else "-")    elif op == "rot":        train.rotate(-k)  # 음수는 왼쪽으로: 앞의 것이 뒤로 간다(빈 deque는 그대로)out.append(str(len(train)))out.append(" ".join(map(str, train)) if train else "-")print("\n".join(out))
JavaMain.java · 44 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.ArrayDeque;import java.util.StringJoiner; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));        int n = Integer.parseInt(in.readLine().trim());        ArrayDeque<Integer> train = new ArrayDeque<>(); // 앞뒤 어느 쪽에서도 넣고 빼는 데 O(1)        StringBuilder out = new StringBuilder();        for (int i = 0; i < n; i++) {            String[] p = in.readLine().trim().split(" ");            long k = Long.parseLong(p[1]);            switch (p[0]) {                case "addf" -> train.addFirst((int) k);                case "addb" -> train.addLast((int) k);                case "cutf", "cutb" -> {                    // 있는 만큼만 뗀다(빈 ArrayDeque에서 removeFirst·pop은 예외를 던진다)                    long take = Math.min(k, train.size());                    StringJoiner cut = new StringJoiner(" ");                    for (long r = 0; r < take; r++) {                        int car = p[0].equals("cutf") ? train.pollFirst() : train.pollLast(); // 뒤는 맨 뒤 차량부터                        cut.add(String.valueOf(car));                    }                    out.append(take == 0 ? "-" : cut.toString()).append('\n');                }                case "rot" -> {                    if (!train.isEmpty()) {                        long r = k % train.size(); // 한 바퀴 돌면 제자리다                        for (long s = 0; s < r; s++) train.addLast(train.pollFirst());                    }                }                default -> throw new IllegalArgumentException(p[0]);            }        }        out.append(train.size()).append('\n');        StringJoiner items = new StringJoiner(" ");        for (int x : train) items.add(String.valueOf(x)); // 앞에서 뒤로 돈다        out.append(train.isEmpty() ? "-" : items.toString()).append('\n');        System.out.print(out);    }}
C#Main.cs · 51 行
using System;using System.Collections.Generic;using System.Text; class Program{    static void Main()    {        int n = int.Parse(Console.ReadLine()!.Trim());        // .NET에는 Deque가 없다. LinkedList<T>는 앞뒤에 넣고 빼는 데 O(1)이다        var train = new LinkedList<int>();        var output = new StringBuilder();        for (int i = 0; i < n; i++)        {            string[] p = Console.ReadLine()!.Trim().Split(' ');            long k = long.Parse(p[1]);            switch (p[0])            {                case "addf": train.AddFirst((int)k); break;                case "addb": train.AddLast((int)k); break;                case "cutf":                case "cutb":                    // 있는 만큼만 뗀다(비었는데 First를 읽으면 null이다)                    long take = Math.Min(k, train.Count);                    var cut = new List<int>();                    for (long r = 0; r < take; r++)                    {                        if (p[0] == "cutf") { cut.Add(train.First!.Value); train.RemoveFirst(); }                        else { cut.Add(train.Last!.Value); train.RemoveLast(); } // 맨 뒤 차량부터                    }                    output.Append(cut.Count == 0 ? "-" : string.Join(" ", cut)).Append('\n');                    break;                case "rot":                    if (train.Count > 0)                    {                        long r = k % train.Count; // 한 바퀴 돌면 제자리다                        for (long s = 0; s < r; s++)                        {                            var node = train.First!;                            train.RemoveFirst();                            train.AddLast(node); // 마디를 그대로 옮긴다                        }                    }                    break;            }        }        output.Append(train.Count).Append('\n');        output.Append(train.Count == 0 ? "-" : string.Join(" ", train)).Append('\n');        Console.Write(output);    }}
C++main.cpp · 45 行
#include <algorithm>#include <deque>#include <iostream>#include <string> int main() {    std::ios::sync_with_stdio(false);    int n;    std::cin >> n;    std::deque<int> train; // 앞뒤 어느 쪽에서도 넣고 빼는 데 O(1)    for (int i = 0; i < n; i++) {        std::string op;        long long k;        std::cin >> op >> k;        if (op == "addf") {            train.push_front((int)k);        } else if (op == "addb") {            train.push_back((int)k);        } else if (op == "cutf" || op == "cutb") {            // 빈 deque에서 front()·pop_front()는 정의되지 않은 동작이라, 있는 만큼만 뗀다            long long take = std::min(k, (long long)train.size());            if (take == 0) std::cout << '-';            for (long long r = 0; r < take; r++) {                if (r > 0) std::cout << ' ';                if (op == "cutf") {                    std::cout << train.front(); // pop_front()는 값을 돌려주지 않는다                    train.pop_front();                } else {                    std::cout << train.back(); // 뗀 차례: 맨 뒤 차량부터                    train.pop_back();                }            }            std::cout << '\n';        } else if (op == "rot") {            if (!train.empty()) {                long long r = k % (long long)train.size(); // 한 바퀴 돌면 제자리다                // [begin, begin + r)이 끝으로 간다: 앞의 r개가 뒤로                std::rotate(train.begin(), train.begin() + r, train.end());            }        }    }    std::cout << train.size() << '\n';    if (train.empty()) std::cout << "-\n";    for (std::size_t i = 0; i < train.size(); i++) std::cout << train[i] << (i + 1 < train.size() ? ' ' : '\n');}