競技プログラミングにおける問題解決戦略

数理的考察に基づくアルゴリズム設計について議論する。以下に代表的な問題パターンとその解法を示す。

「CF1183F Topforces Strikes Back」

最大値選択問題の最適化戦略を検討する。以下の条件を満たす組み合わせを探索する:

  • 単一要素:最大値を選択
  • 二要素:最大値の因数である必要がある
  • 三要素:最大値とその約数の組み合わせ

証明概要

  1. 最大値以外の要素を含む場合、最大値への置換が最適
  2. 二要素の和は最大値単独より小さくなる
  3. 三要素の場合は約数の線形結合を評価

実装コード

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

int main() {
    int T; cin >> T;
    vector<long long> dp(100001);
    for(int k=2; k<=T+1; k++) {
        int Q, M; scanf("%d%d", &Q, &M);
        dp[1] = 1;
        for(int i=2; i<=M; i++) {
            int prev = i - (i-1)/k - 1;
            int pos = prev - dp[prev] + 1;
            dp[i] = pos + (pos-1)/(k-1) + 1;
        }
        while(Q--) {
            int n; scanf("%d", &n);
            printf("%lld ", dp[n]);
        }
        cout << endl;
    }
}

「Stoi2031 フレームワーク」

再帰構造を持つ問題の解法パターン。部分問題の解を再利用する戦略を示す。

解法概要

  1. 操作後の残り要素数をlとおく
  2. 子問題ans_lの解を現在の問題に適用
  3. 反転操作を考慮した位置計算
  4. 削除された要素数を補正

実装コード

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

int main() {
    int T; cin >> T;
    vector<int> memo(100001);
    for(int k=2; k<=T+1; k++) {
        int Q, M; scanf("%d%d", &Q, &M);
        memo[1] = 1;
        for(int i=2; i<=M; i++) {
            int last = i - (i-1)/k - 1;
            int index = last - memo[last] + 1;
            memo[i] = index + (index-1)/(k-1) + 1;
        }
        while(Q--) {
            int n; scanf("%d", &n);
            printf("%d ", memo[n]);
        }
        cout << endl;
    }
}

「HNOI2011 カーノン問題」

集合選択の制約条件を満たす場合の数え上げ問題。以下が重要な性質:

DPによる解法

  1. 無順序集合の条件を順序付き問題に変換
  2. 組み合わせ数A(n,m)の計算
  3. 重複要素を排除した遷移式

数学的証明

  • 任意のi-1要素を選ぶ場合、最終要素は一意に決まる
  • 空集合を含む場合の補正
  • 要素の重複を排除する補正項

実装コード

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

const long long MOD = 1e8+7;

long long power(long long base, long long exp) {
    long long res = 1;
    while(exp) {
        if(exp & 1) res = res * base % MOD;
        base = base * base % MOD;
        exp >>= 1;
    }
    return res;
}

int main() {
    long long N, M;
    cin >> N >> M;
    long long total = power(2, N) - 1;
    
    vector<long long> A(M+2), fact(M+2);
    A[0] = 1, fact[0] = 1;
    
    for(int i=1; i<=M; i++) {
        A[i] = A[i-1] * (total - i + 1) % MOD;
        fact[i] = fact[i-1] * i % MOD;
    }
    
    vector<long long> dp(M+2);
    for(int i=3; i<=M; i++) {
        dp[i] = (A[i-1] - dp[i-1] + MOD) % MOD;
        dp[i] = (dp[i] - dp[i-2] * (i-1) % MOD * (total - i + 2) % MOD + MOD) % MOD;
    }
    
    cout << dp[M] * power(fact[M], MOD-2) % MOD << endl;
}

タグ: 競技プログラミング 動的計画法 約数問題 数え上げ Josephus問題

9月22日 08:08 投稿