ルクの競プロ部屋

学生の競プロ生活を見たい人はどぞ

競プロ精選 100 問 by C++<動的計画法:ナップザック DP>

はじめに

こんにちは!
今回から、5回連続DPです

34 ALDS_10_A - フィボナッチ数

問題はこちらです!
問題文↓

フィボナッチ数列のN番目を求めてください

再帰関数だとO(N²)だけど、前のデータをmemo化しておくことでDPによってO(N)となる基本問題!
コードはこんな感じになりました

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (ll i = 0; i < (n); ++i)
#define rep1(i, n) for (ll i = 1; i < (n); ++i)
#define rrep(i, n) for (ll i = n; i > 0; --i)
#define bitrep(i, n) for (ll i = 0; i < (1 << n); ++i)
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define yesNo(b) ((b) ? "Yes" : "No")
using ll = long long;
using ull = unsigned long long;
using ld = long double;
string alphabet = "abcdefghijklmnopqrstuvwxyz";
string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const double pi = 3.141592653589793;
int smallMOD = 998244353;
int bigMOD = 1000000007;

int main()
{
    vector<int> A = {1, 1};
    int n;
    cin >> n;
    rep(i, n - 1)
    {
        A.push_back(A[i] + A[i + 1]);
    }
    cout << A[n] << endl;
    return 0;
}

全てのデータをvectorに保存しなくても良い気もする?

35 DPL_1_B - 0,1ナップザック問題

問題はこちらです!
問題文↓

重さWを超えないようにN個のものから価値が最大になるように選んでください

DPの基本問題でした!
コードはこんな感じです

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (ll i = 0; i < (n); ++i)
#define rep1(i, n) for (ll i = 1; i < (n); ++i)
#define rrep(i, n) for (ll i = n; i > 0; --i)
#define bitrep(i, n) for (ll i = 0; i < (1 << n); ++i)
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define yesNo(b) ((b) ? "Yes" : "No")
using ll = long long;
using ull = unsigned long long;
using ld = long double;
string alphabet = "abcdefghijklmnopqrstuvwxyz";
string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const double pi = 3.141592653589793;
int smallMOD = 998244353;
int bigMOD = 1000000007;

int main()
{
    int n, w;
    cin >> n >> w;
    vector<pair<int, int>> items(n);
    rep(i, n)
    {
        int weight, value;
        cin >> value >> weight;
        items[i] = make_pair(weight, value);
    }
    // ナップザックDP
    vector<vector<int>> dp(n + 1, vector<int>(w + 1, 0));
    rep(i, n)
    {
        rep(j, w + 1)
        {
            if (j - items[i].first >= 0)
            {
                dp[i + 1][j] = max(dp[i][j], dp[i][j - items[i].first] + items[i].second);
            }
            else
            {
                dp[i + 1][j] = dp[i][j];
            }
        }
    }
    cout << dp[n][w] << endl;
    return 0;
}

基本問題なのでcopilotがほぼ書いてくれました(よくない)

36 DPL_1_C - ナップザック問題

問題はこちらです!
問題文↓

# 35に要素を追加する
・同じアイテムを何回も選ぶことが出来る

簡単なDPです!
コードはこんな感じ

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (ll i = 0; i < (n); ++i)
#define rep1(i, n) for (ll i = 1; i < (n); ++i)
#define rrep(i, n) for (ll i = n; i > 0; --i)
#define bitrep(i, n) for (ll i = 0; i < (1 << n); ++i)
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define yesNo(b) ((b) ? "Yes" : "No")
using ll = long long;
using ull = unsigned long long;
using ld = long double;
string alphabet = "abcdefghijklmnopqrstuvwxyz";
string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const double pi = 3.141592653589793;
int smallMOD = 998244353;
int bigMOD = 1000000007;

int main()
{
    int n, w;
    cin >> n >> w;
    vector<pair<int, int>> items(n);
    rep(i, n)
    {
        int weight, value;
        cin >> value >> weight;
        items[i] = make_pair(weight, value);
    }
    // 同じアイテムも選ぶことが出来るナップザックDP
    vector<int> dp(w + 1, 0);
    rep(i, n)
    {
        rep(j, w + 1)
        {
            if (j - items[i].first >= 0)
            {
                dp[j] = max(dp[j], dp[j - items[i].first] + items[i].second);
            }
        }
    }
    cout << dp[w] << endl;
    return 0;
}

DP練習しなきゃ,,,

37 DPL_1_A - コイン問題

問題はこちらです!
問題文↓

いくつかの額面がバラバラのコインが与えられる
それらを組み合わせてn円を作成する
そのときの最小のコインの枚数はいくつか
同じコインを使用してもよい

コードはこんな感じ

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (ll i = 0; i < (n); ++i)
#define rep1(i, n) for (ll i = 1; i < (n); ++i)
#define rrep(i, n) for (ll i = n; i > 0; --i)
#define bitrep(i, n) for (ll i = 0; i < (1 << n); ++i)
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define yesNo(b) ((b) ? "Yes" : "No")
using ll = long long;
using ull = unsigned long long;
using ld = long double;
string alphabet = "abcdefghijklmnopqrstuvwxyz";
string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const double pi = 3.141592653589793;
int smallMOD = 998244353;
int bigMOD = 1000000007;

int main()
{
    int n, m;
    cin >> n >> m;
    vector<int> coins(m);
    rep(i, m) cin >> coins[i];
    // coinでn円を作る最小の枚数のDP
    vector<int> dp(n + 1, 100000);
    dp[0] = 0;
    rep(i, m)
    {
        rep(j, n + 1)
        {
            if (j - coins[i] >= 0)
            {
                dp[j] = min(dp[j], dp[j - coins[i]] + 1);
            }
        }
    }
    cout << dp[n] << endl;
    return 0;
}

基本問題はcopilotに任せて、コードを理解することに専念します

38 ALDS_10_C - 最長共通部分列

問題はこちらです!
問題文↓

最長共通部分列とは、{x_1,x_2,x_3,...,x_n}と{y_1,y_2,y_3,...,y_m}という文字列があった時、いくつかの文字を取り出して等しくなった文字列のうち、最長の長さのものです
例えば、abcdeはacdという文字列を部分列として持っており、acdfという文字列もacdという部分列を持っています
このacdという文字列を共通部分列ということにします
この共通部分列のうち、最長のものの長さを求めてください

コードはこんな感じ

#include <bits/stdc++.h>
using namespace std;
#define rep(i, n) for (ll i = 0; i < (n); ++i)
#define rep1(i, n) for (ll i = 1; i < (n); ++i)
#define rrep(i, n) for (ll i = n; i > 0; --i)
#define bitrep(i, n) for (ll i = 0; i < (1 << n); ++i)
#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define yesNo(b) ((b) ? "Yes" : "No")
using ll = long long;
using ull = unsigned long long;
using ld = long double;
string alphabet = "abcdefghijklmnopqrstuvwxyz";
string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const double pi = 3.141592653589793;
int smallMOD = 998244353;
int bigMOD = 1000000007;

int main()
{
    int n;
    cin >> n;
    // 最長共通部分列問題をDPで解く
    rep(i, n)
    {
        string x, y;
        cin >> x >> y;
        int m = x.size();
        int l = y.size();
        vector<vector<int>> dp(m + 1, vector<int>(l + 1, 0));
        rep(i, m)
        {
            rep(j, l)
            {
                if (x[i] == y[j])
                {
                    dp[i + 1][j + 1] = dp[i][j] + 1;
                }
                else
                {
                    dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j]);
                }
            }
        }
        cout << dp[m][l] << endl;
    }
    return 0;
}

めっちゃむずい、、、

おわりに

お疲れ様でした!
次は真面目にDPの精進をします
ではまた!