從CF1941D與1741E初探可達性DP

加固文明幻景發表於2024-03-13

Problem - D - Codeforces

用記憶化搜尋過的,然而DP能快300ms

記憶化搜尋 | \(\texttt{set}\)模擬

核心思路一致,都是透過定義一個狀態,即在第t次到達第now點來去重剪枝

記憶化搜尋

int n, m, x;
std::vector<std::pair<int, char>> step;
std::set<int> S;

int getClock(int x, int dis) {
    dis %= n;
    if (x + dis > n) return (x + dis) % n;
    else return x + dis;
}

int getAntiClock(int x, int dis) {
    dis %= n;
    if (x - dis < 1) return n - (dis - (x - 1)) + 1; 
    else return x - dis;
}

bool vis[1010][1010];

void dfs(int now, int t) {
    if (t == m) {S.insert(now); return ;}
    if (vis[now][t]) {return ;}
    vis[now][t] = true;
    auto[dis, opt] = step[t];
    if (opt == '?' or opt == '0') {
        dfs(getClock(now, dis), t + 1);
    }
    if (opt == '?' or opt == '1') {
        dfs(getAntiClock(now, dis), t + 1);
    }
}

\(\texttt{set}\)​模擬

這裡STD還利用了幾個取模trick

首先是把 \(x \mod{n}\) 轉化成 \((x - 1)\mod {n} + 1\) ,防止 \(x = n\) 時想要得到 \(n\) 卻得到 \(0\)

首先是逆時針可能是負數,所以最後再加上 \(n\) 防止取模失效。

//本質上也是記憶化搜尋
//透過set自動把當前位置且次數都相同的狀態去重了

void solve() {
    int n, m, x;
    std::cin >> n >> m >> x;
    std::set<int> S[2];
    bool cur(false);
    S[cur].insert(x);
    while (m--) {
        int d; char opt;
        std::cin >> d >> opt;
        while (!S[cur].empty()) {
            int now(*S[cur].begin());
            S[cur].erase(now);
            if (opt == '?' or opt == '0') {
                S[cur ^ 1].insert((now + d - 1) % n + 1);
            }
            if (opt == '?' or opt == '1') {
                S[cur ^ 1].insert((now - d + n - 1) % n + 1);
            }
        }
        cur ^= 1;
    }
    std::cout << sz(S[cur]) << '\n';
    for (auto& x : S[cur]) std::cout << x << ' ';
    std::cout << '\n';
}

可達性DP

即定義狀態 \(dp_{i, j}\) 為第 \(i\) 次操作第 \(j\) 點的可達性。

轉移方程很直接

\[dp_{i, j} = dp_{i - 1, (j + d - 1)\mod n + 1}(opt = \text{?} \| opt = \text{0}) \]

\[dp_{i, j} = dp_{i - 1, (j - d + n - 1)\mod n + 1}(opt = \text{?} \| opt = \text{1}) \]

原始寫法

因為該題沒有卡空間,所以能過

void solve() {
    int n, m, x;
    std::cin >> n >> m >> x;
    std::vector dp(m + 1, std::vector<short>(n + 1));
    dp[0][x] = 1;
    for (int i = 1; i <= m; i++) {
        int d; char opt;
        std::cin >> d >> opt;
        if (opt == '?' or opt == '0') {
            for (int j = 1; j <= n; j++) {
                dp[i][j] |= dp[i - 1][(j - d + n - 1) % n + 1];
            }
        }
        if (opt == '?' or opt == '1') {
            for (int j = 1; j <= n; j++) {
                dp[i][j] |= dp[i - 1][(j + d - 1) % n + 1];
            }
        }
    }
    std::cout << std::count(all(dp[m]), 1) << '\n';
    for (int i = 0; i <= n; i++) if (dp[m][i] == 1) {
        std::cout << i << ' ';
    }
    std::cout << '\n';
}

最佳化空間

t寶和jls都是進一步最佳化了空間,因為該狀態只會從上一步轉移而來,所以完全可以省去第一維

void solve() {
    int n, m, x;
    std::cin >> n >> m >> x;
    x--;
    std::vector<bool> dp(n);
    dp[x] = true;
    for (int i = 0; i < m; i++) {
        int d; char opt;
        std::cin >> d >> opt;
        std::vector<bool> newDp(n);
        for (int j = 0; j < n; j++) if(dp[j]) {//從0開始,直接避免了取模會為0的問題
            if (opt != '1') {
                newDp[(j + d) % n] = true;
            }
            if (opt != '0') {
                newDp[(j - d + n) % n] = true;
            }
        }
        dp = newDp;
    }
    std::cout << std::count(all(dp), 1) << '\n';
    for (int i = 0; i < n; i++) if (dp[i]) {
        std::cout << i + 1 << ' ';
    }
    std::cout << '\n';
}

另一道題

Problem - E - Codeforces

依然可以用可達性DP。

定義 \(dp_i\)\([1, i]\) 區間是否合法

假設 \(a_i\)​ 就是表示區間長度的值,則透過之前合法的情況遞推

這裡取 \(i - 1\) 是因為 \(i\) 時表示長度的那個數的下標,不用算進去

如果他在他表示區間的左邊,則 \([1, i + a_i]\) 的合法條件是 \([1, i - 1]\) 合法

如果他在他表示區間的右邊,則 \([1, i]\) 的合法條件是 \([1, i - 1 - a_i]\) 合法

void solve() {
    int n;
    std::cin >> n;
    std::vector<int> a(n + 1);
    for (int i = 1; i <= n; i++) std::cin >> a[i];
    std::vector<bool> dp(n + 1);
    dp[0] = true;
    for (int i = 1; i <= n; i++) {
        if (i + a[i] <= n and dp[i - 1]) {
            dp[i + a[i]] = true;
        }
        if (i - 1 - a[i] >= 0 and dp[i - 1 - a[i]]) {
            dp[i] = true;
        }
    }
    dp[n] ? YES : NO;
}

相關文章