競技プログラミングにおける代表的アルゴリズムテンプレート集

高精度計算

トライ木を用いたA+B

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
using namespace std;

struct TrieNode {
    int children[26];
    int value;
} nodes[1000];

char buffer[100];
int nodeCount = 0, totalNodes = 0, resultSum = 0;
bool negativeFlag;

void insertNumber() {
    int current = 0;
    for (int i = 0; buffer[i] != '\0'; i++) {
        if (buffer[i] == '-') {
            negativeFlag = true;
            continue;
        }
        int digit = buffer[i] - '0';
        if (nodes[current].children[digit] == 0)
            nodes[current].children[digit] = ++totalNodes;
        current = nodes[current].children[digit];
        nodes[current].value = digit;
    }
}

int queryNumber() {
    int current = 0, result = 0;
    for (int i = 0; buffer[i] != '\0'; i++) {
        if (buffer[i] == '-') continue;
        int digit = buffer[i] - '0';
        if (nodes[current].children[digit] == 0) return result;
        current = nodes[current].children[digit];
        result = result * 10 + nodes[current].value;
    }
    return result;
}

int main() {
    for (int i = 1; i <= 2; i++) {
        negativeFlag = false;
        scanf("%s", buffer);
        insertNumber();
        if (negativeFlag)
            resultSum -= queryNumber();
        else
            resultSum += queryNumber();
    }
    printf("%d\n", resultSum);
    return 0;
}

筆算によるA+B (文字列処理)

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

int numA[100], numB[100], result[100];
int lenA, lenB, resultLen = 1;

int main() {
    string strA, strB;
    cin >> strA >> strB;
    lenA = strA.length();
    lenB = strB.length();
    for (int i = 1; i <= lenA; i++) numA[i] = strA[lenA - i] - 48;
    for (int i = 1; i <= lenB; i++) numB[i] = strB[lenB - i] - 48;
    int carry = 0;
    while (resultLen <= lenA || resultLen <= lenB) {
        result[resultLen] = numA[resultLen] + numB[resultLen] + carry;
        carry = result[resultLen] / 10;
        result[resultLen] %= 10;
        resultLen++;
    }
    result[resultLen] = carry;
    if (result[resultLen] == 0) resultLen--;
    for (int i = resultLen; i > 0; i--) cout << result[i];
    return 0;
}

A×B (簡易高精度)

基本データ構造

便利なマクロとUtility

#include <bits/stdc++.h>
using namespace std;

#define MAX(a,b) ((a)>(b)?(a):(b))
#define MIN(a,b) ((a)<(b)?(a):(b))

inline int fastRead() {
    int f = 1, x = 0;
    char ch;
    do { ch = getchar(); if (ch == '-') f = -1; } while (ch < '0' || ch > '9');
    do { x = x * 10 + ch - '0'; ch = getchar(); } while (ch >= '0' && ch <= '9');
    return f * x;
}

inline void fastPrint(int x) {
    if (x < 0) { putchar('-'); fastPrint(-x); return; }
    if (x >= 10) fastPrint(x / 10);
    putchar(x % 10 + '0');
}

// 疑似128ビット整数 (long long 2つ組み合わせ)
using ll = long long;
const ll BASE = 1e9;
struct BigInt {
    ll low, high;
    BigInt() : low(0), high(0) {}
    BigInt(ll v) { low = v % BASE; high = v / BASE; }
    friend BigInt operator+(const BigInt &a, const BigInt &b) {
        BigInt c;
        c.low = a.low + b.low;
        c.high = a.high + b.high;
        if (c.low < 0 && c.high > 0) { c.high--; c.low += BASE; }
        if (c.low >= BASE) { c.high += c.low / BASE; c.low %= BASE; }
        return c;
    }
    friend bool operator<(const BigInt &a, const BigInt &b) {
        if (a.high != b.high) return a.high < b.high;
        return a.low < b.low;
    }
};
// cin高速化
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);

リンクリスト

struct Node {
    Node *prev, *next;
    int data;
    Node(int d) : prev(nullptr), next(nullptr), data(d) {}
};

void insertAfter(Node *x, Node *y) {
    if (x->next) {
        y->next = x->next;
        y->next->prev = y;
    }
    x->next = y;
    y->prev = x;
}

void removeNode(Node *x) {
    if (x->prev) x->prev->next = x->next;
    if (x->next) x->next->prev = x->prev;
    delete x;
}

スタックとキュー (配列実装)

struct MyStack {
    int data[100010];
    int top;
    MyStack() : top(0) {}
    void push(int x) { data[++top] = x; }
    void pop() { if (top) top--; }
    int size() { return top; }
    int query() { return data[top]; }
};

struct MyQueue {
    int data[100010];
    int front, rear;
    MyQueue() : front(0), rear(0) {}
    void push(int x) {
        if (rear == 100000) rear = 1; else rear++;
        data[rear] = x;
    }
    void pop() {
        if (front == 100000) front = 1; else front++;
    }
    int getFront() { return data[front]; }
    int size() {
        if (front < rear) return rear - front;
        else return front - rear + 1;
    }
    bool empty() { return front == rear; }
};

単調キュー (Min/Max スライド窓)

#include <stdio.h>
const int N = 1000000 + 10;

inline int read() {
    int x = 0, f = 1; char ch;
    do { ch = getchar(); if (ch == 45) f = -1; } while (ch < 48 || ch > 57);
    do { x = x * 10 + ch - 48; ch = getchar(); } while (ch >= 48 && ch <= 57);
    return f * x;
}

struct SlidingWindow {
    int values[N], indices[N];
    int l, r;
    void init() { l = 0; r = -1; }
    void push(int idx, int val) {
        while (l <= r && values[r] >= val) r--;
        values[++r] = val;
        indices[r] = idx;
    }
    int getMin(int idx, int k) {
        while (l <= r && idx - indices[l] >= k) l++;
        return values[l];
    }
} w;

void slideMin(int a[], int n, int k) {
    w.init();
    for (int i = 0; i < n; i++) {
        w.push(i, a[i]);
        if (i >= k - 1) printf("%d ", w.getMin(i, k));
    }
    printf("\n");
}

優先度付きキュー (ヒープ)

#include <queue>
using namespace std;
priority_queue<int> maxHeap;                         // 最大値
priority_queue<int, vector<int>, greater<int>> minHeap; // 最小値

文字列ハッシュ

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
using ull = unsigned long long;

const ull BASE = 131;
const ull MOD = 212370440130137957ull;
const ull PRIME = 233317;
ull hashVal[10010];
char buf[10010];

ull hashStr(char s[]) {
    int len = strlen(s);
    ull res = 0;
    for (int i = 0; i < len; i++)
        res = (res * BASE + (ull)s[i]) % MOD + PRIME;
    return res;
}

int main() {
    int n; scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%s", buf);
        hashVal[i] = hashStr(buf);
    }
    sort(hashVal, hashVal + n);
    int ans = 1;
    for (int i = 1; i < n; i++)
        if (hashVal[i] != hashVal[i-1]) ans++;
    printf("%d\n", ans);
    return 0;
}

トライ (Trie)

#include <cstdio>
#include <cstring>

int trie[1000010][62], cnt[1000010], idx;
char str[10010];

int charIndex(char c) {
    if (c >= 'A' && c <= 'Z') return c - 'A';
    if (c >= 'a' && c <= 'z') return c - 'a' + 26;
    return c - '0' + 52;
}

void insert(char s[]) {
    int p = 0, len = strlen(s);
    for (int i = 0; i < len; i++) {
        int c = charIndex(s[i]);
        if (!trie[p][c]) trie[p][c] = ++idx;
        p = trie[p][c];
        cnt[p]++;
    }
}

int query(char s[]) {
    int p = 0, len = strlen(s);
    for (int i = 0; i < len; i++) {
        int c = charIndex(s[i]);
        if (!trie[p][c]) return 0;
        p = trie[p][c];
    }
    return cnt[p];
}

グラフと木

グラフの保存

連鎖前方型 (Chain Forward Star)

struct Edge {
    int to, next, w;
} edges[1000000];
int head[1000000], edgeCount = 0;

void addEdge(int u, int v, int w = 0) {
    edges[++edgeCount].to = v;
    edges[edgeCount].w = w;
    edges[edgeCount].next = head[u];
    head[u] = edgeCount;
}

単一始点最短路

Floyd-Warshall

#include <bits/stdc++.h>
using namespace std;
#define INF 123456789
int dist[10005][10005], n, m, s;

void floyd() {
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++) {
            if (i == k || dist[i][k] == INF) continue;
            for (int j = 1; j <= n; j++)
                dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
        }
}

SPFA (Bellman-Ford 改良版)

#include <queue>
#include <cstdio>
const int N = 10005, M = 500005, INF = 1e9;
int head[N], n, m, s;
struct Edge { int to, next, w; } ed[M];
int dis[N]; bool vis[N];

void spfa() {
    for (int i = 1; i <= n; i++) dis[i] = INF;
    dis[s] = 0;
    queue<int> q; q.push(s); vis[s] = true;
    while (!q.empty()) {
        int u = q.front(); q.pop(); vis[u] = false;
        for (int i = head[u]; i; i = ed[i].next) {
            int v = ed[i].to;
            if (dis[u] + ed[i].w < dis[v]) {
                dis[v] = dis[u] + ed[i].w;
                if (!vis[v]) { vis[v] = true; q.push(v); }
            }
        }
    }
}

ダイクストラ (優先度付きキュー最適化)

#include <queue>
#include <cstdio>
using namespace std;
const int N = 100005, INF = 2147483647;
int head[N], cnt;
struct Edge { int to, next, w; } e[N];
int dis[N]; bool vis[N];

struct Node {
    int dist, idx;
    bool operator<(const Node &other) const { return dist > other.dist; }
};

void dijkstra(int s) {
    for (int i = 1; i <= N; i++) dis[i] = INF;
    dis[s] = 0;
    priority_queue<Node> pq;
    pq.push({0, s});
    while (!pq.empty()) {
        Node cur = pq.top(); pq.pop();
        int u = cur.idx;
        if (vis[u]) continue;
        vis[u] = true;
        for (int i = head[u]; i; i = e[i].next) {
            int v = e[i].to;
            if (dis[v] > dis[u] + e[i].w) {
                dis[v] = dis[u] + e[i].w;
                pq.push({dis[v], v});
            }
        }
    }
}

トポロジカルソート

最小全域木

クラスカル法

木の直径と重心

LCA (最近共通祖先)

動的計画法 (DP)

線形DP

最長増加部分列 (LIS)

#include <algorithm>
using namespace std;
int a[100], dp[100], n;
int lis() {
    int ans = 0;
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
        for (int j = 0; j < i; j++)
            if (a[i] > a[j]) dp[i] = max(dp[i], dp[j] + 1);
        ans = max(ans, dp[i]);
    }
    return ans;
}

最長共通部分列 (LCS)

int lcs(char *A, char *B, int n, int m) {
    int dp[100][100] = {};
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            if (A[i-1] == B[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
            else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
    return dp[n][m];
}

ナップザックDP

01 ナップザック

int weight[50010], value[50010], dp[50010];
int n, capacity;
int main() {
    cin >> n >> capacity;
    for (int i = 1; i <= n; i++) cin >> weight[i] >> value[i];
    for (int i = 1; i <= n; i++)
        for (int j = capacity; j >= weight[i]; j--)
            dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
    cout << dp[capacity];
}

完全ナップザック (個数制限なし)

int n, capacity;
int w, v, dp[50010];
int main() {
    scanf("%d%d", &n, &capacity);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &w, &v);
        for (int j = w; j <= capacity; j++)
            dp[j] = max(dp[j], dp[j - w] + v);
    }
    printf("%d", dp[capacity]);
}

グループナップザック

int dp[50010], group[10010][10010], count[10010];
int main() {
    int capacity, n; cin >> capacity >> n;
    int w, v, g, maxGroup = 0;
    for (int i = 1; i <= n; i++) {
        cin >> w >> v >> g;
        maxGroup = max(maxGroup, g);
        count[g]++;
        group[g][count[g]] = i; // インデックスを保存
    }
    for (int g = 1; g <= maxGroup; g++)
        for (int j = capacity; j >= 0; j--)
            for (int k = 1; k <= count[g]; k++) {
                // 実際の重みと価値は別途配列で持つ
            }
}

探索 (Search)

深さ優先探索 (DFS) - 例:n-Queen

int n, ans, col[20], diag1[40], diag2[40];
void search(int y) {
    if (y == n) { ans++; return; }
    for (int x = 0; x < n; x++) {
        if (col[x] || diag1[x+y] || diag2[x-y+n-1]) continue;
        col[x] = diag1[x+y] = diag2[x-y+n-1] = 1;
        search(y+1);
        col[x] = diag1[x+y] = diag2[x-y+n-1] = 0;
    }
}

幅優先探索 (BFS) - 例:迷路の最短距離

#include <queue>
#include <utility>
using namespace std;
int maze[100][100], dist[100][100];
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
int h, w;

void bfs(int sx, int sy) {
    queue<pair<int,int>> q;
    q.push({sx, sy});
    dist[sx][sy] = 0;
    while (!q.empty()) {
        auto [x, y] = q.front(); q.pop();
        for (int k = 0; k < 4; k++) {
            int nx = x + dx[k], ny = y + dy[k];
            if (nx < 0 || nx >= h || ny < 0 || ny >= w) continue;
            if (maze[nx][ny] == 1 || dist[nx][ny] != -1) continue;
            dist[nx][ny] = dist[x][y] + 1;
            q.push({nx, ny});
        }
    }
}

二分探索と貪欲法

二分探索 (一般的な実数値探索)

double findRoot(double l, double r, double eps) {
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (f(mid) == 0) return mid;
        if (f(l) * f(mid) < 0) r = mid;
        else l = mid;
    }
    return l;
}

STL二分探索

#include <algorithm>
#include <vector>
using namespace std;
vector<int> v = {1, 3, 5, 7, 9};
auto it = lower_bound(v.begin(), v.end(), 5); // 最初の5以上の要素
size_t idx = it - v.begin();

数学

素数関連

試し割りによる判定

bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return false;
    return true;
}

エラトステネスの篩

bool isComposite[100000001];
int primes[20000001], pcnt;
void sieve(int n) {
    for (int i = 2; i <= n; i++) {
        if (!isComposite[i]) primes[++pcnt] = i;
        for (int j = 1; j <= pcnt && i * primes[j] <= n; j++) {
            isComposite[i * primes[j]] = true;
            if (i % primes[j] == 0) break;
        }
    }
}

GCD & LCM

int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
int lcm(int a, int b) { return a / gcd(a, b) * b; }

高速累乗 (繰り返し二乗法)

int fastPow(int a, int b, int mod) {
    int res = 1;
    while (b > 0) {
        if (b & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

行列累乗 (フィボナッチ数列)

struct Mat {
    long long m[2][2];
    Mat() { memset(m, 0, sizeof(m)); }
    Mat operator*(const Mat &b) const {
        Mat c;
        for (int i = 0; i < 2; i++)
            for (int k = 0; k < 2; k++)
                for (int j = 0; j < 2; j++)
                    c.m[i][j] = (c.m[i][j] + m[i][k] * b.m[k][j]) % MOD;
        return c;
    }
};

Mat matPow(Mat a, long long n) {
    Mat res; res.m[0][0] = res.m[1][1] = 1;
    while (n) {
        if (n & 1) res = res * a;
        a = a * a;
        n >>= 1;
    }
    return res;
}

その他頻出テクニック

累積和 (Prefix Sum)

long long arr[100005], prefix[100005];
int n;
for (int i = 1; i <= n; i++) prefix[i] = prefix[i - 1] + arr[i];
// [l, r] 区間和 = prefix[r] - prefix[l-1]

差分 (Difference Array)

long long diff[100005];
void addRange(int l, int r, long long val) {
    diff[l] += val;
    diff[r + 1] -= val;
}
// 戻し方: for (int i = 1; i <= n; i++) arr[i] = arr[i-1] + diff[i];

Union-Find (Disjoint Set Union)

int parent[100010];
int find(int x) { return parent[x] == x ? x : parent[x] = find(parent[x]); }
void unite(int x, int y) { parent[find(x)] = find(y); }
int main() {
    int n, m; scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) parent[i] = i;
    while (m--) {
        int op, x, y; scanf("%d%d%d", &op, &x, &y);
        if (op == 1) unite(x, y);
        else puts(find(x) == find(y) ? "Y" : "N");
    }
}

KMP (文字列検索)

#include <iostream>
#include <cstring>
using namespace std;
char text[1000010], pattern[1000010];
int lps[1000010];

int main() {
    cin >> text+1 >> pattern+1;
    int n = strlen(text+1), m = strlen(pattern+1);
    // LPS テーブル構築
    lps[1] = 0;
    for (int i = 2, len = 0; i <= m; i++) {
        while (len > 0 && pattern[i] != pattern[len+1]) len = lps[len];
        if (pattern[i] == pattern[len+1]) len++;
        lps[i] = len;
    }
    // マッチング
    for (int i = 1, j = 0; i <= n; i++) {
        while (j > 0 && text[i] != pattern[j+1]) j = lps[j];
        if (text[i] == pattern[j+1]) j++;
        if (j == m) {
            cout << i - m + 1 << endl;
            j = lps[j];
        }
    }
}

K番目の要素 (std::nth_element)

#include <algorithm>
#include <vector>
vector<int> v = {3, 1, 4, 1, 5, 9, 2};
nth_element(v.begin(), v.begin() + 2, v.end()); // 3番目 (0-indexed) が確定
// v[2] が小さい方から3番目になる

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

7月3日 20:44 投稿