1. 程式人生 > 實用技巧 >Codeforces Round #658 (Div. 2) D. Unmerge(dp)

Codeforces Round #658 (Div. 2) D. Unmerge(dp)

題目連結:https://codeforces.com/contest/1382/problem/D

題意

給出一個大小為 $2n$ 的排列,判斷能否找到兩個長為 $n$ 的子序列,使得二者歸併排序後能夠得到該排列。

題解

將原排列拆分為一個個連續子序列,每次從大於上一子序列首部的元素處分出下一連續子序列,只要將這些子序列按照拆分先後排列,歸併排序後一定可以得到原排列。

之後即判斷能否將這些子序列排列為兩個長為 $n$ 的序列即可,可以用狀壓 $dp$,也可以用 $01$ 揹包。

狀態 $dp$:每次將之前的每一個可行長度加上當前長度得到新一批的可行長度,然後將當前長度標記為可行。

$01$ 揹包:將每個子序列的長度視為其花費與價值,最後判斷花費為 $n$ 的揹包總價值是否為 $n$ 即可。

程式碼一

狀壓 $dp$:$O_{(n)}$

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

void solve() {
    int n; cin >> n;
    int mx = 0;
    vector<int> idx;
    for (int i = 0; i < 2 * n; ++i) {
        int x; cin >> x;
        if (x > mx) {
            mx = x;
            idx.push_back(i);
        }
    }
    idx.push_back(2 * n);
    vector<int> len;
    for (int i = 1; i < idx.size(); ++i) {
        len.push_back(idx[i] - idx[i - 1]);
    }
    bitset<2020> dp;
    for (auto i : len) {
        dp |= dp << i;
        dp[i] = 1;
    }
    cout << (dp[n] ? "YES" : "NO") << "\n";
}

int main() {
    int t; cin >> t;
    while (t--) solve();
}

程式碼二

$01$ 揹包:$O_{(vn)}$

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

void solve() {
    int n; cin >> n;
    int mx = 0;
    vector<int> idx;
    for (int i = 0; i < 2 * n; ++i) {
        int x; cin >> x;
        if (x > mx) {
            mx = x;
            idx.push_back(i);
        }
    }
    idx.push_back(2 * n);
    int N = idx.size() - 1, p = 0;
    int cost[N] = {}, val[N] = {};
    for (int i = 1; i < idx.size(); ++i) {
        cost[p] = val[p] = idx[i] - idx[i - 1];
        ++p;
    }
    map<int, int> dp;
    for (int i = 0; i < N; ++i) {
        for (int j = n; j >= cost[i]; --j) {
            dp[j] = max(dp[j], dp[j - cost[i]] + val[i]);
        }
    }
    cout << (dp[n] == n ? "YES" : "NO") << "\n";
}

int main() {
    int t; cin >> t;
    while (t--) solve();
}