{ }codelper
比較入れ子の条件null 値P0301

基準の順に比べる商品比較

家電売り場の比較アシスタントが、二つの商品のどちらが良いかを教える。
商品は n 個で 1 番から n 番までの番号があり、商品ごとに 1 番から k 番までの k 個の欄に測定値(整数)が書かれている。
測定していない欄には数の代わりに x が書かれている。
比較の基準が r 個、重要な順に与えられる。
基準一つ "f d e" は f 番の欄を見るという意味である。
d が H なら測定値が大きいほうが良く、L なら測定値が小さいほうが良い。
e は許容誤差である。
同じ欄を見る基準が二回出ることはない。
二つの商品を比べるときは、最初の基準から順に次のように調べる。
二つの商品ともその欄が x なら、この基準では引き分けである。
片方だけが x なら、d が何であっても測定値があるほうが良い。
両方に測定値があり、二つの測定値の差(大きい数から小さい数を引いた値)が e 以下なら、この基準では引き分けである。
差が e より大きければ、d が H のときは測定値が大きいほうが、L のときは測定値が小さいほうが良い。
引き分けなら次の基準に進み、良いほうが決まったらそこで止める。
質問が q 個与えられ、質問一つ "a b" は a 番の商品と b 番の商品を比べよという意味である(a と b が同じこともある)。
質問ごとに一行に、良いほうの商品の番号と、勝負を決めた基準が何番目の基準か(最初の基準が 1)を空白一つで区切って出力せよ。
すべての基準で引き分けなら 0 だけを出力する。

入力
一行目に n、k、r、q が空白で区切られて与えられる。
次の n 行に 1 番の商品から順に k 個の欄の値(整数または x)が空白一つで区切られて与えられる。
次の r 行に基準 "f d e" が、次の q 行に質問 "a b" が一行に一つずつ与えられる。
2 ≤ n ≤ 1,000、1 ≤ k ≤ 8、1 ≤ r ≤ k、1 ≤ q ≤ 10,000、-2,000,000,000 ≤ 測定値 ≤ 2,000,000,000、1 ≤ f ≤ k、d は H または L、0 ≤ e ≤ 1,000,000,000、1 ≤ a, b ≤ n
出力
q 行。
各行は「良いほうの商品の番号、勝負を決めた基準の順番」または 0
例 1 · stdin
4 3 3 5
300 45 x
320 48 7
300 60 7
300 44 x
2 L 5
3 H 1
1 H 0
1 2
1 3
1 4
2 3
4 2
stdout
2 2
1 1
0
2 1
2 2
main.py
入力(stdin)

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

$ 準備完了

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

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

二つの値を比べた結果は「前が良い・後ろが良い・引き分け」の三つで、そこに「値がない」まで割り込みます。
だから聞く順番が大事です:① 両方ないか、② 片方だけないか、③ 差が誤差の内か、④ どちらが大きいか。
向きは (u > v) == high のように「大きいほうが u か」と「大きいほうが良いか」が同じかどうかで一度に聞けます。
落とし穴その一:測定値は ±20 億なので 32 ビットの int に入りますが、二つの値の差は 40 億まで行ってあふれます。
Java の Math.abs(u - v) と C# の int の引き算はエラーなしに一周して、でたらめな小さい数になり(20 億 −(−20 億)が −294,967,296)、C・C++ では未定義動作です。
Java・C# は long、C・C++ は long long で受けて引き、C の abs は int 用なので llabs を使います。
Python の整数には上限がなく、JS・TS・Craykon の数は 2^53 まで正確なのでそのままで大丈夫です。
落とし穴その二:「値がない」をとても小さい数で代用すると、H の基準では合いますが、L の基準(小さいほど良い)ではないほうが勝ってしまいます。
「ない」は別に持ちます:Python None、JS・TS null(TS は number | null なので、取り除く前に引くとコンパイルエラー)、C# long?、C++ std::optional<long long>、Craykon なし、Java・C は値があるかを記す配列を別に持ちます。
「ない」を取り除かずに比べると言語ごとに違います:Python の None < 5 は TypeError、Java は Long が null なら NullPointerException、Craykon は「なし(値がない)では計算できません」と止まります。
黙って通るほうが危険です:JS は null を 0 に変えて比べ(null < 5 は真)、C# の long? は片方が null なら < も > も偽で、C++ の optional は空のほうをいつも小さいとします。
落とし穴その三:境界です。
差がちょうど e のときは引き分けなので <= であって < ではありません。
落とし穴その四:JS で split で分けた文字列を数に変えないと、u - v は数に変わって計算されますが、u > v は文字の順で比べるので "10" > "9" が偽になります。
Python は文字列どうしを引くと TypeError、Craykon も「- の計算は数どうしでしかできません」と止まるのですぐ分かります。
落とし穴その五:出力するのは基準が見る欄の番号 f ではなく、その基準が何番目かです。

Pythonmain.py · 33 行
import sys  def compare(x, y, rules):    """(앞선 쪽, 가른 기준의 차례)를 돌려준다. 앞선 쪽은 x면 1, y면 -1, 끝까지 비기면 0."""    for j, (f, high, e) in enumerate(rules, 1):        u, v = x[f], y[f]        if u is None and v is None:  # 둘 다 값이 없으면 이 기준은 비긴다            continue        if u is None:                # 값이 없는 쪽이 진다(방향과 상관없이)            return -1, j        if v is None:            return 1, j        if abs(u - v) <= e:          # 차이가 허용 오차 이하이면 비긴다            continue        return (1 if (u > v) == high else -1), j    return 0, 0  lines = sys.stdin.read().split("\n")n, k, r, q = map(int, lines[0].split())items = [[None if t == "x" else int(t) for t in line.split()] for line in lines[1 : 1 + n]]rules = []for line in lines[1 + n : 1 + n + r]:    f, d, e = line.split()    rules.append((int(f) - 1, d == "H", int(e))) out = []for line in lines[1 + n + r : 1 + n + r + q]:    a, b = map(int, line.split())    side, j = compare(items[a - 1], items[b - 1], rules)    out.append("0" if side == 0 else f"{a if side > 0 else b} {j}")print("\n".join(out))
JavaScriptmain.js · 31 行
const lines = require("fs").readFileSync(0, "utf8").split("\n");const [n, k, r, q] = lines[0].split(" ").map(Number);// 값이 없는 칸은 null. Number("x")는 NaN이라 따로 가른다const items = lines.slice(1, 1 + n).map((line) => line.trim().split(" ").map((t) => (t === "x" ? null : Number(t))));const rules = lines.slice(1 + n, 1 + n + r).map((line) => {  const [f, d, e] = line.trim().split(" ");  return { f: Number(f) - 1, high: d === "H", e: Number(e) };}); // [앞선 쪽, 가른 기준의 차례]: 앞선 쪽은 x면 1, y면 -1, 끝까지 비기면 0function compare(x, y) {  for (let j = 0; j < rules.length; j++) {    const { f, high, e } = rules[j];    const u = x[f];    const v = y[f];    if (u === null && v === null) continue; // 둘 다 값이 없으면 이 기준은 비긴다    if (u === null) return [-1, j + 1]; // 값이 없는 쪽이 진다(방향과 상관없이)    if (v === null) return [1, j + 1];    if (Math.abs(u - v) <= e) continue; // 차이가 허용 오차 이하이면 비긴다    return [u > v === high ? 1 : -1, j + 1];  }  return [0, 0];} const out = [];for (const line of lines.slice(1 + n + r, 1 + n + r + q)) {  const [a, b] = line.split(" ").map(Number);  const [side, j] = compare(items[a - 1], items[b - 1]);  out.push(side === 0 ? "0" : `${side > 0 ? a : b} ${j}`);}console.log(out.join("\n"));
TypeScriptmain.ts · 41 行
import { readFileSync } from "node:fs"; interface Rule {  f: number; // 볼 칸(0부터)  high: boolean; // 클수록 좋은가  e: number; // 허용 오차} const lines: string[] = readFileSync(0, "utf8").split("\n");const [n, k, r, q]: number[] = lines[0].split(" ").map(Number);// 값이 없는 칸은 null. 타입이 number | null이라 null을 거르지 않고 빼면 컴파일러가 막는다const items: (number | null)[][] = lines  .slice(1, 1 + n)  .map((line: string) => line.trim().split(" ").map((t: string): number | null => (t === "x" ? null : Number(t))));const rules: Rule[] = lines.slice(1 + n, 1 + n + r).map((line: string): Rule => {  const [f, d, e]: string[] = line.trim().split(" ");  return { f: Number(f) - 1, high: d === "H", e: Number(e) };}); // [앞선 쪽, 가른 기준의 차례]: 앞선 쪽은 x면 1, y면 -1, 끝까지 비기면 0function compare(x: (number | null)[], y: (number | null)[]): [number, number] {  for (let j = 0; j < rules.length; j++) {    const { f, high, e }: Rule = rules[j];    const u: number | null = x[f];    const v: number | null = y[f];    if (u === null && v === null) continue; // 둘 다 값이 없으면 이 기준은 비긴다    if (u === null) return [-1, j + 1]; // 값이 없는 쪽이 진다(방향과 상관없이)    if (v === null) return [1, j + 1];    if (Math.abs(u - v) <= e) continue; // 차이가 허용 오차 이하이면 비긴다    return [u > v === high ? 1 : -1, j + 1];  }  return [0, 0];} const out: string[] = [];for (const line of lines.slice(1 + n + r, 1 + n + r + q)) {  const [a, b]: number[] = line.split(" ").map(Number);  const [side, j]: [number, number] = compare(items[a - 1], items[b - 1]);  out.push(side === 0 ? "0" : `${side > 0 ? a : b} ${j}`);}console.log(out.join("\n"));
JavaMain.java · 60 行
import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.util.StringTokenizer; public class Main {    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 k = Integer.parseInt(st.nextToken());        int r = Integer.parseInt(st.nextToken());        int q = Integer.parseInt(st.nextToken());         long[][] value = new long[n][k];     // 두 값의 차이가 40억까지라 long        boolean[][] has = new boolean[n][k]; // 값이 있는 칸인가("x"면 false)        for (int i = 0; i < n; i++) {            st = new StringTokenizer(in.readLine());            for (int c = 0; c < k; c++) {                String t = st.nextToken();                has[i][c] = !t.equals("x");                if (has[i][c]) value[i][c] = Long.parseLong(t);            }        }        int[] field = new int[r];        boolean[] high = new boolean[r];        long[] tol = new long[r];        for (int j = 0; j < r; j++) {            st = new StringTokenizer(in.readLine());            field[j] = Integer.parseInt(st.nextToken()) - 1;            high[j] = st.nextToken().equals("H");            tol[j] = Long.parseLong(st.nextToken());        }         StringBuilder sb = new StringBuilder();        for (int t = 0; t < q; t++) {            st = new StringTokenizer(in.readLine());            int a = Integer.parseInt(st.nextToken());            int b = Integer.parseInt(st.nextToken());            int winner = 0; // 앞선 상품 번호(끝까지 비기면 0)            int decided = 0; // 가른 기준의 차례            for (int j = 0; j < r && winner == 0; j++) {                int f = field[j];                boolean hasA = has[a - 1][f], hasB = has[b - 1][f];                if (!hasA && !hasB) continue; // 둘 다 값이 없으면 이 기준은 비긴다                if (!hasA || !hasB) {         // 값이 없는 쪽이 진다(방향과 상관없이)                    winner = hasA ? a : b;                } else {                    long u = value[a - 1][f], v = value[b - 1][f];                    if (Math.abs(u - v) <= tol[j]) continue; // 차이가 허용 오차 이하이면 비긴다                    winner = (u > v) == high[j] ? a : b;                }                decided = j + 1;            }            if (winner == 0) sb.append("0\n");            else sb.append(winner).append(' ').append(decided).append('\n');        }        System.out.print(sb);    }}
C#Main.cs · 57 行
using System;using System.Text; class Program{    static void Main()    {        string[] head = Console.ReadLine().Trim().Split(' ');        int n = int.Parse(head[0]), k = int.Parse(head[1]), r = int.Parse(head[2]), q = int.Parse(head[3]);         // long?는 값이 없을 수 있는 long이다("x"면 null). 두 값의 차이가 40억까지라 int로는 모자란다        var items = new long?[n][];        for (int i = 0; i < n; i++)        {            string[] w = Console.ReadLine().Trim().Split(' ');            items[i] = new long?[k];            for (int c = 0; c < k; c++) items[i][c] = w[c] == "x" ? (long?)null : long.Parse(w[c]);        }        var field = new int[r];        var high = new bool[r];        var tol = new long[r];        for (int j = 0; j < r; j++)        {            string[] w = Console.ReadLine().Trim().Split(' ');            field[j] = int.Parse(w[0]) - 1;            high[j] = w[1] == "H";            tol[j] = long.Parse(w[2]);        }         var sb = new StringBuilder();        for (int t = 0; t < q; t++)        {            string[] w = Console.ReadLine().Trim().Split(' ');            int a = int.Parse(w[0]), b = int.Parse(w[1]);            int winner = 0;  // 앞선 상품 번호(끝까지 비기면 0)            int decided = 0; // 가른 기준의 차례            for (int j = 0; j < r && winner == 0; j++)            {                long? u = items[a - 1][field[j]], v = items[b - 1][field[j]];                if (u == null && v == null) continue; // 둘 다 값이 없으면 이 기준은 비긴다                if (u == null || v == null)                {                    winner = u != null ? a : b; // 값이 없는 쪽이 진다(방향과 상관없이)                }                else                {                    if (Math.Abs(u.Value - v.Value) <= tol[j]) continue; // 차이가 허용 오차 이하이면 비긴다                    winner = (u.Value > v.Value) == high[j] ? a : b;                }                decided = j + 1;            }            if (winner == 0) sb.Append("0\n");            else sb.Append(winner).Append(' ').Append(decided).Append('\n');        }        Console.Write(sb.ToString());    }}
C++main.cpp · 58 行
#include <cstdlib>#include <iostream>#include <optional>#include <string>#include <vector>using namespace std; struct Rule {    int f;       // 볼 칸(0부터)    bool high;   // 클수록 좋은가    long long e; // 허용 오차}; int main() {    ios::sync_with_stdio(false);    cin.tie(nullptr);    int n, k, r, q;    cin >> n >> k >> r >> q;     // optional은 값이 없을 수 있는 칸이다("x"면 nullopt). 두 값의 차이가 40억까지라 long long    vector<vector<optional<long long>>> items(n, vector<optional<long long>>(k));    for (auto& row : items) {        for (auto& cell : row) {            string token;            cin >> token;            if (token != "x") cell = stoll(token);        }    }    vector<Rule> rules(r);    for (Rule& rule : rules) {        char dir;        cin >> rule.f >> dir >> rule.e;        rule.f--;        rule.high = dir == 'H';    }     while (q--) {        int a, b;        cin >> a >> b;        int winner = 0;  // 앞선 상품 번호(끝까지 비기면 0)        int decided = 0; // 가른 기준의 차례        for (int j = 0; j < r && winner == 0; j++) {            const optional<long long>& u = items[a - 1][rules[j].f];            const optional<long long>& v = items[b - 1][rules[j].f];            if (!u && !v) continue; // 둘 다 값이 없으면 이 기준은 비긴다            if (!u || !v) {         // 값이 없는 쪽이 진다(방향과 상관없이)                winner = u ? a : b;            } else {                if (llabs(*u - *v) <= rules[j].e) continue; // 차이가 허용 오차 이하이면 비긴다                winner = (*u > *v) == rules[j].high ? a : b;            }            decided = j + 1;        }        if (winner == 0) cout << "0\n";        else cout << winner << ' ' << decided << '\n';    }    return 0;}
Cmain.c · 53 行
#include <stdio.h>#include <stdlib.h>#include <string.h> #define MAXN 1000#define MAXK 8 static long long value[MAXN][MAXK]; /* 두 값의 차이가 40억까지라 long long */static int has[MAXN][MAXK];         /* 값이 있는 칸인가("x"면 0) */static int field[MAXK], high[MAXK];static long long tol[MAXK]; int main(void) {    int n, k, r, q;    if (scanf("%d %d %d %d", &n, &k, &r, &q) != 4) return 0;    for (int i = 0; i < n; i++) {        for (int c = 0; c < k; c++) {            char token[16];            scanf("%15s", token);            has[i][c] = strcmp(token, "x") != 0;            if (has[i][c]) value[i][c] = atoll(token);        }    }    for (int j = 0; j < r; j++) {        char dir[4];        scanf("%d %3s %lld", &field[j], dir, &tol[j]);        field[j]--;        high[j] = dir[0] == 'H';    }     for (int t = 0; t < q; t++) {        int a, b;        scanf("%d %d", &a, &b);        int winner = 0;  /* 앞선 상품 번호(끝까지 비기면 0) */        int decided = 0; /* 가른 기준의 차례 */        for (int j = 0; j < r && winner == 0; j++) {            int f = field[j];            int hasA = has[a - 1][f], hasB = has[b - 1][f];            if (!hasA && !hasB) continue; /* 둘 다 값이 없으면 이 기준은 비긴다 */            if (!hasA || !hasB) {         /* 값이 없는 쪽이 진다(방향과 상관없이) */                winner = hasA ? a : b;            } else {                long long u = value[a - 1][f], v = value[b - 1][f];                if (llabs(u - v) <= tol[j]) continue; /* 차이가 허용 오차 이하이면 비긴다 */                winner = (u > v) == high[j] ? a : b;            }            decided = j + 1;        }        if (winner == 0) printf("0\n");        else printf("%d %d\n", winner, decided);    }    return 0;}
Craykonmain.cray · 81 行
// [앞선 쪽, 가른 기준의 차례]: 앞선 쪽은 x면 1, y면 -1, 끝까지 비기면 0견주기(x, y, rules){    繰り返す (j = 0; j < rules.個数; j++)    {        f = rules[j][0];        high = rules[j][1];        u = x[f];        v = y[f];        もし (u == なし && v == なし)        {            次へ;        }        もし (u == なし)        {            返す [-1, j + 1];        }        もし (v == なし)        {            返す [1, j + 1];        }        もし (数学.絶対値(u - v) <= rules[j][2])        {            次へ;        }        もし (u > v == high)        {            返す [1, j + 1];        }        返す [-1, j + 1];    }    返す [0, 0];} 첫줄 = コンソール.数読み();n = 첫줄[0];r = 첫줄[2];q = 첫줄[3]; items = [];繰り返す (i = 0; i < n; i++){    칸들 = [];    それぞれ (t の中 コンソール.行読み().分ける())    {        もし (t == "x")        {            칸들.追加(なし); // 값이 없는 칸. 빈값으로 빼거나 견주면 멈추므로 먼저 걸러야 한다        }        でなければ        {            칸들.追加(t.数に());        }    }    items.追加(칸들);} rules = []; // [볼 칸(0부터), 클수록 좋은가, 허용 오차]繰り返す (j = 0; j < r; j++){    w = コンソール.行読み().分ける();    rules.追加([w[0].数に() - 1, w[1] == "H", w[2].数に()]);} 繰り返す (t = 0; t < q; t++){    쌍 = コンソール.数読み();    결과 = 견주기(items[쌍[0] - 1], items[쌍[1] - 1], rules);    もし (결과[0] == 0)    {        コンソール.書く(0);    }    でなければ もし (결과[0] > 0)    {        コンソール.書く(쌍[0] + " " + 결과[1]);    }    でなければ    {        コンソール.書く(쌍[1] + " " + 결과[1]);    }}