C++による競技プログラミング問題の解決アプローチ

基本的な比較計算

二つの整数の積を比較する問題では、直接的な計算を用いる。


#include <iostream>
int main() {
    long long w, x, y, z;
    std::cin >> w >> x >> y >> z;
    std::cout << (w * x < y * z ? "First" : "Second") << '\n';
    return 0;
}

数値精度の比較

長さ最大2×10⁵の文字列で与えられる小数を、小数点以下6桁まで比較する。整数部分は小数として扱い、精度を保証する。


#include <string>
#include <iostream>

std::string ensurePrecision(const std::string& num) {
    std::string temp = num;
    if(temp.find('.') == std::string::npos) temp += '.';
    temp += "000000";
    return temp.substr(0, temp.find('.') + 7);
}

int main() {
    std::string numA, numB;
    std::cin >> numA >> numB;
    std::cout << (ensurePrecision(numA) == ensurePrecision(numB) ? "EQUAL" : "DIFF") << '\n';
    return 0;
}

グリッドの接続性解析

2×nグリッドにおいて、隣接セルの連結性をグラフとして捉え、切断点(関節点)を特定する。孤立点は特別に扱う。


#include <vector>
#include <string>
#include <algorithm>
using namespace std;

const int MAX_N = 1000010;
vector<int> graph[MAX_N];
int discovery[MAX_N], low[MAX_N], timer = 0;
bool isCritical[MAX_N];

void dfs(int node, int parent) {
    discovery[node] = low[node] = ++timer;
    int childCount = 0;
    for(int neighbor : graph[node]) {
        if(neighbor == parent) continue;
        if(!discovery[neighbor]) {
            childCount++;
            dfs(neighbor, node);
            low[node] = min(low[node], low[neighbor]);
            if(low[neighbor] >= discovery[node] && parent != -1) {
                isCritical[node] = true;
            }
        } else {
            low[node] = min(low[node], discovery[neighbor]);
        }
    }
    if(parent == -1 && childCount > 1) isCritical[node] = true;
}

int main() {
    int cols;
    cin >> cols;
    string rowA, rowB;
    cin >> rowA >> rowB;
    int totalCells = 2 * cols;
    for(int r = 0; r < 2; ++r) {
        for(int c = 0; c < cols; ++c) {
            if((r == 0 ? rowA[c] : rowB[c]) == '.') continue;
            int currentId = r * cols + c;
            bool isolated = true;
            int dr[] = {0, 0, 1, -1};
            int dc[] = {1, -1, 0, 0};
            for(int dir = 0; dir < 4; ++dir) {
                int nr = r + dr[dir], nc = c + dc[dir];
                if(nr >= 0 && nr < 2 && nc >= 0 && nc < cols) {
                    char neighborChar = (nr == 0 ? rowA[nc] : rowB[nc]);
                    if(neighborChar == 'x') continue;
                    int neighborId = nr * cols + nc;
                    graph[currentId].push_back(neighborId);
                    isolated = false;
                }
            }
            if(isolated) totalCells--;
        }
    }
    for(int i = 0; i < 2 * cols; ++i) {
        if(!discovery[i]) dfs(i, -1);
    }
    for(int i = 0; i < 2 * cols; ++i) {
        if(isCritical[i]) totalCells++;
    }
    cout << totalCells << '\n';
    return 0;
}

連結成分の最適な色変換

グラフの各連結成分において、最も多い色に統一するのが最適である。変更数は成分サイズから最大色の出現数を引いた値となる。


#include 
#include <queue>
#include <vector>
#include <iostream>
using namespace std;

int main() {
    int vertices, edges;
    cin >> vertices >> edges;
    vector<int> colors(vertices + 1);
    for(int i = 1; i <= vertices; ++i) cin >> colors[i];
    vector adjList(vertices + 1);
    for(int i = 0; i < edges; ++i) {
        int u, v;
        cin >> u >> v;
        adjList[u].push_back(v);
        adjList[v].push_back(u);
    }
    vector<bool> visited(vertices + 1, false);
    auto processComponent = [&](int start) -> int {
        unordered_map colorFreq;
        queue<int> q;
        q.push(start);
        visited[start] = true;
        int componentSize = 0;
        int maxFreq = 0;
        while(!q.empty()) {
            int current = q.front(); q.pop();
            componentSize++;
            colorFreq[colors[current]]++;
            maxFreq = max(maxFreq, colorFreq[colors[current]]);
            for(int neighbor : adjList[current]) {
                if(!visited[neighbor]) {
                    visited[neighbor] = true;
                    q.push(neighbor);
                }
            }
        }
        return componentSize - maxFreq;
    };
    int totalCost = 0;
    for(int i = 1; i <= vertices; ++i) {
        if(!visited[i]) {
            totalCost += processComponent(i);
        }
    }
    cout << totalCost << '\n';
    return 0;
}

括弧ペアの削除順序

スタックを用いて各括弧ペアに番号を割り当て、削除不可能な開き括弧の数を記録することで効率的に計算する。


#include <stack>
#include <string>
#include <vector>
#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    string seq;
    cin >> seq;
    seq = " " + seq;
    vector<int> pairId(2 * n + 1, 0);
    vector<int> cannotRemove(n + 1, 0);
    stack<int> posStack;
    int currentPair = 0;
    for(int i = 1; i <= 2 * n; ++i) {
        if(seq[i] == '(') {
            posStack.push(i);
            pairId[i] = ++currentPair;
        } else {
            if(posStack.empty()) {
                cout << "-1\n";
                return 0;
            }
            int openPos = posStack.top();
            posStack.pop();
            pairId[i] = pairId[openPos];
            cannotRemove[pairId[openPos]] = posStack.size();
        }
    }
    for(int i = 1; i <= n; ++i) {
        cout << n - cannotRemove[i] << (i == n ? "\n" : " ");
    }
    return 0;
}

特殊な数列の区間クエリ処理

セグメント木を用いて区間積と特殊な累積和を効率的に管理する。更新は等比数列の和で計算する。


#include <functional>
#include <vector>
#include <iostream>
using namespace std;

const long long MOD = 1000000007;

struct ModNum {
    long long val;
    ModNum(long long v = 0) : val(v % MOD) {}
    ModNum operator*(ModNum other) const { return ModNum(val * other.val % MOD); }
    ModNum operator+(ModNum other) const { return ModNum((val + other.val) % MOD); }
    ModNum operator-(ModNum other) const { return ModNum((val - other.val + MOD) % MOD); }
    ModNum power(long long exp) const {
        ModNum result(1), base = *this;
        while(exp > 0) {
            if(exp & 1) result = result * base;
            base = base * base;
            exp >>= 1;
        }
        return result;
    }
    ModNum inverse() const { return power(MOD - 2); }
    ModNum operator/(ModNum other) const { return *this * other.inverse(); }
};

struct SegmentNode {
    int left, right;
    ModNum product;
    ModNum specialSum;
    ModNum lazy;
    SegmentNode() : left(0), right(0), product(1), specialSum(0), lazy(0) {}
};

class SegmentTree {
    vector<SegmentNode> tree;
    int n;
    void applyLazy(int idx, ModNum value) {
        int length = tree[idx].right - tree[idx].left + 1;
        tree[idx].lazy = value;
        tree[idx].product = value.power(length);
        if(value.val == 1) {
            tree[idx].specialSum = ModNum(length);
        } else {
            tree[idx].specialSum = (value.power(length + 1) - value) / (value - ModNum(1));
        }
    }
    void pushDown(int idx) {
        if(tree[idx].lazy.val != 0) {
            applyLazy(idx * 2, tree[idx].lazy);
            applyLazy(idx * 2 + 1, tree[idx].lazy);
            tree[idx].lazy = ModNum(0);
        }
    }
    void combine(SegmentNode& parent, const SegmentNode& leftChild, const SegmentNode& rightChild) {
        parent.product = leftChild.product * rightChild.product;
        parent.specialSum = leftChild.specialSum + leftChild.product * rightChild.specialSum;
    }
public:
    SegmentTree(const vector<int>& arr) : n(arr.size() - 1) {
        tree.resize(4 * n + 5);
        function build = [&](int idx, int l, int r) {
            tree[idx].left = l; tree[idx].right = r;
            tree[idx].lazy = ModNum(0);
            if(l == r) {
                ModNum val(arr[l]);
                tree[idx].product = val;
                tree[idx].specialSum = val;
                return;
            }
            int mid = (l + r) / 2;
            build(idx * 2, l, mid);
            build(idx * 2 + 1, mid + 1, r);
            combine(tree[idx], tree[idx * 2], tree[idx * 2 + 1]);
        };
        build(1, 1, n);
    }
    void rangeUpdate(int idx, int ql, int qr, ModNum value) {
        if(tree[idx].left > qr || tree[idx].right < ql) return;
        if(tree[idx].left >= ql && tree[idx].right <= qr) {
            applyLazy(idx, value);
            return;
        }
        pushDown(idx);
        rangeUpdate(idx * 2, ql, qr, value);
        rangeUpdate(idx * 2 + 1, ql, qr, value);
        combine(tree[idx], tree[idx * 2], tree[idx * 2 + 1]);
    }
    SegmentNode rangeQuery(int idx, int ql, int qr) {
        if(tree[idx].left >= ql && tree[idx].right <= qr) return tree[idx];
        pushDown(idx);
        int mid = (tree[idx].left + tree[idx].right) / 2;
        if(qr <= mid) return rangeQuery(idx * 2, ql, qr);
        if(ql > mid) return rangeQuery(idx * 2 + 1, ql, qr);
        SegmentNode leftResult = rangeQuery(idx * 2, ql, qr);
        SegmentNode rightResult = rangeQuery(idx * 2 + 1, ql, qr);
        SegmentNode merged;
        merged.left = ql; merged.right = qr;
        combine(merged, leftResult, rightResult);
        return merged;
    }
};

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    int size, queries;
    cin >> size >> queries;
    vector<int> initial(size + 1);
    for(int i = 1; i <= size; ++i) cin >> initial[i];
    SegmentTree seg(initial);
    while(queries--) {
        int type;
        cin >> type;
        if(type == 1) {
            int l, r, x;
            cin >> l >> r >> x;
            seg.rangeUpdate(1, l, r, ModNum(x));
        } else {
            int l, r;
            cin >> l >> r;
            cout << seg.rangeQuery(1, l, r).specialSum.val << '\n';
        }
    }
    return 0;
}

タグ: C++ 競技プログラミング アルゴリズム データ構造 グラフ理論

6月28日 22:57 投稿