競技プログラミング問題集: 生成器、MEX、XORの応用

理想的な生成器の判定

正整数kが「理想生成器」であるとは、任意の整数n(n ≥ k)が、長さkの回文配列の要素和として表現可能な場合を指す。回文配列とは、配列aがa1からakまでとakからa1までが同一となる配列である。例として、k=1は理想生成器である(nは[n]で表現可能)が、k=2は非理想(3を表現不可能)。

解法: kが奇数の場合のみ理想生成器となる。偶数の場合、配列和が奇数値を取り得ないため。

実装例
#include <iostream>
using namespace std;

int main() {
    int test_cases;
    cin >> test_cases;
    while (test_cases--) {
        int generator;
        cin >> generator;
        cout << (generator % 2 ? "YES" : "NO") << endl;
    }
    return 0;
}

高価な数字の最適化

正整数nの「コスト」はnを各桁の和で除算した値と定義される。nから任意の桁を除去し(先頭ゼロ許容)、残りの数字でコストを最小化せよ。

解法: 最下位の非ゼロ桁を保持し、それより高位の全桁を除去する。これによりコスト1を達成可能。

実装例
#include <cstdio>
#include <cstring>

int main() {
    int cases;
    scanf("%d", &cases);
    while (cases--) {
        char digits[200];
        scanf("%s", digits);
        int length = strlen(digits);
        int trailing_zeros = 0;
        bool non_zero_found = false;
        for (int i = length - 1; i >= 0; i--) {
            non_zero_found |= (digits[i] != '0');
            trailing_zeros += (non_zero_found && digits[i] == '0');
        }
        printf("%d\n", length - trailing_zeros - 1);
    }
    return 0;
}

素数生成の単純な反復

数字xをk回繰り返して生成された数yが素数か判定せよ(例: x=52, k=3 → y=525252)。

解法: k>1かつx≠1の場合、yは合成数。x=1かつk=2の場合のみ素数(y=11)。

実装例
#include <iostream>
using namespace std;

bool is_prime(long num) {
    if (num <= 1) return false;
    for (long i = 2; i * i <= num; i++)
        if (num % i == 0) return false;
    return true;
}

int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        long base;
        int repetitions;
        cin >> base >> repetitions;
        if (base == 1 && repetitions == 2) {
            cout << "YES\n";
        } else if (repetitions > 1) {
            cout << "NO\n";
        } else {
            cout << (is_prime(base) ? "YES" : "NO") << "\n";
        }
    }
    return 0;
}

再帰的なテーブル埋め

2n×2nテーブルを再帰的に4分割し、特定順序で1から22nまで埋める。クエリに応じて座標→値または値→座標を回答。

解法: 再帰的に領域を4分割し、各領域のオフセットとサイズを管理。値計算には累積加算を、座標計算には再帰探索を適用。

実装例
#include <cstdio>
typedef long long ll;

void locate_value(ll value, int &row, int &col, int size) {
    if (!size) return;
    ll segment = (ll)size * size;
    if (value < segment) {
        locate_value(value, row, col, size >> 1);
    } else if (value < 2 * segment) {
        value -= segment;
        row += size;
        col += size;
        locate_value(value, row, col, size >> 1);
    } else if (value < 3 * segment) {
        value -= 2 * segment;
        row += size;
        locate_value(value, row, col, size >> 1);
    } else {
        value -= 3 * segment;
        col += size;
        locate_value(value, row, col, size >> 1);
    }
}

ll compute_value(ll row, ll col, ll start_r, ll start_c, ll size) {
    if (!size) return 0;
    ll end_r = start_r + size, end_c = start_c + size;
    if (row < end_r && col < end_c) {
        return compute_value(row, col, start_r, start_c, size >> 1);
    } else if (row >= end_r && col >= end_c) {
        return size * size + compute_value(row, col, end_r, end_c, size >> 1);
    } else if (row >= end_r) {
        return 2 * size * size + compute_value(row, col, end_r, start_c, size >> 1);
    }
    return 3 * size * size + compute_value(row, col, start_r, end_c, size >> 1);
}

int main() {
    int tests;
    scanf("%d", &tests);
    while (tests--) {
        int dim, queries;
        scanf("%d %d", &dim, &queries);
        ll grid_size = 1LL << (dim - 1);
        while (queries--) {
            char op;
            scanf(" %c", &op);
            if (op == '?') {
                ll x, y;
                scanf("%lld %lld", &x, &y);
                x--; y--;
                printf("%lld\n", compute_value(x, y, 0, 0, grid_size) + 1);
            } else {
                ll num;
                scanf("%lld", &num);
                int r = 0, c = 0;
                locate_value(num - 1, r, c, grid_size);
                printf("%d %d\n", r + 1, c + 1);
            }
        }
    }
    return 0;
}

サブアレイ分割とMEX最大化

配列をk個の連続サブアレイに分割し、各サブアレイのMEX(最小非負整数)の最小値を最大化せよ。

解法: 二分探索で目標MEX値を設定。達成可能かは貪欲法で検証:左からサブアレイを形成し、MEXが目標値以上なら分割。

実装例
#include <iostream>
#include <vector>
using namespace std;

bool can_achieve(vector<int> &data, int segments, int target) {
    vector<bool> exists(target + 2, false);
    int current = 0, count = 0;
    for (int i = 0; i < data.size(); i++) {
        if (data[i] <= target && !exists[data[i]]) {
            exists[data[i]] = true;
            if (++current == target) {
                current = 0;
                count++;
                fill(exists.begin(), exists.end(), false);
            }
        }
    }
    return count >= segments;
}

int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        int n, k;
        cin >> n >> k;
        vector<int> arr(n);
        for (int i = 0; i < n; i++) cin >> arr[i];
        int low = 0, high = n;
        while (low < high) {
            int mid = (low + high + 1) >> 1;
            if (can_achieve(arr, k, mid)) low = mid;
            else high = mid - 1;
        }
        cout << high << endl;
    }
    return 0;
}

文字列配列変換の最小操作

空白配列を目標配列aに変換する最小操作数を求めよ。操作:ニューラルネットワークiを選択し、空白位置にbi,jを挿入、または任意位置を空白に戻す。

解法: 各位置でajと一致するbi,jが存在必須。最適戦略:aと最も一致するbiを選択後、不一致位置を個別修正。

実装例
#include <iostream>
#include <vector>
using namespace std;

int main() {
    int tests;
    cin >> tests;
    while (tests--) {
        int len, nets;
        cin >> len >> nets;
        vector<string> target(len);
        vector<vector<string>> neural_outs(nets, vector<string>(len));
        vector<bool> feasible(len, false);
        for (int i = 0; i < len; i++) cin >> target[i];
        for (int i = 0; i < nets; i++) {
            for (int j = 0; j < len; j++) {
                cin >> neural_outs[i][j];
                feasible[j] = feasible[j] || (neural_outs[i][j] == target[j]);
            }
        }
        for (int i = 0; i < len; i++) {
            if (!feasible[i]) {
                cout << "-1\n";
                goto next_test;
            }
        }
        int max_match = 0;
        for (int i = 0; i < nets; i++) {
            int matches = 0;
            for (int j = 0; j < len; j++) 
                matches += (neural_outs[i][j] == target[j]);
            max_match = max(max_match, matches);
        }
        cout << 3 * len - 2 * max_match << endl;
        next_test:;
    }
    return 0;
}

美しいサブアレイの最短長

配列からXOR美麗値(任意要素対のXOR最大値)がk以上となる最短サブアレイの長さを求めよ。

解法: 各要素を右端とするサブアレイで、美麗値k以上を達成する最左端を二分探索。永続トライ木で効率的にXOR最大値計算。

実装例
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

struct PersistentTrie {
    struct Node {
        int ch[2] = {0, 0};
        int cnt = 0;
    };
    vector<Node> nodes;
    vector<int> roots;
    int ncnt = 1;

    void init() {
        nodes.resize(1);
        roots.push_back(0);
    }

    void insert(int val) {
        roots.push_back(ncnt++);
        nodes.resize(ncnt);
        int cur = roots.back(), prev = roots[roots.size()-2];
        for (int i = 30; i >= 0; i--) {
            int bit = (val >> i) & 1;
            nodes[cur].ch[!bit] = nodes[prev].ch[!bit];
            nodes[cur].ch[bit] = ncnt++;
            nodes.resize(ncnt);
            cur = nodes[cur].ch[bit];
            prev = nodes[prev].ch[bit];
            nodes[cur].cnt = nodes[prev].cnt + 1;
        }
    }

    int max_xor(int l, int r, int val) {
        int res = 0, left = roots[l], right = roots[r];
        for (int i = 30; i >= 0; i--) {
            int bit = (val >> i) & 1;
            if (nodes[nodes[right].ch[!bit]].cnt - nodes[nodes[left].ch[!bit]].cnt > 0) {
                res |= (1 << i);
                left = nodes[left].ch[!bit];
                right = nodes[right].ch[!bit];
            } else {
                left = nodes[left].ch[bit];
                right = nodes[right].ch[bit];
            }
        }
        return res;
    }
};

int main() {
    int tests;
    scanf("%d", &tests);
    while (tests--) {
        int n, k;
        scanf("%d %d", &n, &k);
        vector<int> arr(n);
        for (int i = 0; i < n; i++) scanf("%d", &arr[i]);
        if (k == 0) {
            printf("1\n");
            continue;
        }
        PersistentTrie trie;
        trie.init();
        trie.insert(0);
        for (int num : arr) trie.insert(num);
        int ans = INT_MAX;
        for (int r = 1; r < n; r++) {
            int l = 0, pos = r;
            while (l <= r) {
                int mid = (l + r) / 2;
                if (trie.max_xor(mid, r + 1, arr[r]) >= k) {
                    ans = min(ans, r - mid + 1);
                    l = mid + 1;
                } else {
                    r = mid - 1;
                }
            }
        }
        printf("%d\n", ans == INT_MAX ? -1 : ans);
    }
    return 0;
}

タグ: 競技プログラミング アルゴリズム データ構造 数学 貪欲法

6月26日 21:45 投稿