Atcoder Educational DP Contest 部分做题记录

G.Longest Path

拓扑排序\(dp\)

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int dp[100010], d[100010];
void solve() {
    int n, m; scanf("%d%d", &n, &m);
    vector<int> g[n + 1];
    for (int i = 1;i <= m;i++) {
        int a, b;
        scanf("%d%d", &a, &b);
        g[a].push_back(b);
        d[b] ++;
    }
    queue<int> q;
    for (int i = 1;i <= n;i++) if (!d[i]) q.push(i);
    while (!q.empty()) {
        int x = q.front(); q.pop();
        for (auto y : g[x]) {
            if (dp[x] + 1 > dp[y]) {
                
                dp[y] = dp[x] + 1;
            }
            d[y] --;
            if (!d[y]) q.push(y);
        }
    }
    printf("%d\n", *max_element(dp + 1, dp + n + 1));
}
 
int main() {
    solve();
    return 0;
}

I.Coins

\(dp_{i,j}\)表示,当前枚举到第\(i\)个硬笔,有\(j\)个硬币正面向上的概率。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
constexpr int mod = 1e9 + 7;
long double dp[3010][3010];
 
void solve() {
    int n; cin >> n;
    vector<long double> p(n + 1);
    for (int i = 1;i <= n;i++) cin >> p[i];
    dp[1][0] = 1.0 - p[1];
    dp[1][1] = p[1];
    for (int i = 2;i <= n;i++) {
        for (int j = 0;j <= i;j++) {
            dp[i][j] = dp[i - 1][j] * (1.0 - p[i]);
            if (j > 0) dp[i][j] += dp[i - 1][j - 1] * p[i];
        }
    }
    long double ans = 0;
    for (int i = n / 2 + 1;i <= n;i++) ans += dp[n][i];
    cout << fixed << setprecision(10) << ans << endl;
}
 
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    solve();
    return 0;
}

K.Stones

博弈论中,如果一个状态能够转移到必败态,则该状态为必胜态。
预处理出所有的必败态,由必败态进行状态转移。

#include <bits/stdc++.h>
 
using namespace std;
using ll = long long;
 
int dp[100010], vis[100010];
int a[110];
void solve() {
    int n, k; cin >> n >> k;
    set<int> s;
    for (int i = 0;i < n;i++) {
        int x; cin >> x;
        s.insert(x);
        dp[x] = 1;
        vis[x] = 1;
        a[i] = x;
    }
    for (int i = 0;i < *s.begin();i++) dp[i] = 0, vis[i] = 1;
    for (int i = 0;i <= k;i++) {
        if (vis[i]) continue;
        vis[i] = 1;
        for (auto v : s) {
            if (i - v >= 0) {
                dp[i] |= (dp[i - v] == 0 ? 1 : 0);
            }
            else break;
        }
    }
    cout << (dp[k] == 1 ? "First\n" : "Second\n");
}
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    solve();
}

L.Deque

两个人都想要自己的得分最大化。
\(dp_{l,r}\)为在\(l-r\)这一段进行游戏的最终\(x-y\)的大小。
那么我们可以类似搜索,用区间\(dp\)求解。

#include <bits/stdc++.h>
#define pb push_back
#define endl '\n'
using namespace std;
using ll = long long;

ll dp[3010][3010];
void solve() {
    int n; cin >> n;
    vector<ll> a(n + 1);
    memset(dp, -0x3f, sizeof dp);
    for (int i = 1; i <= n;i++) {
        cin >> a[i];
        dp[i][i] = a[i];
    }
    for (int len = 2;len <= n;len ++) {
        for (int l = 1;l <= n;l ++) {
            int r = l + len - 1;
            dp[l][r] = max(a[l] - dp[l + 1][r], a[r] - dp[l][r - 1]);
        }
    }

    cout << dp[1][n] << endl;

}
signed main(void) {
    cin.tie(nullptr), cout.tie(nullptr) -> ios::sync_with_stdio(false);
    solve(); 
    return 0;
}

M.Candies

\(dp_{i,j}\)表示到第\(i\)个人,分\(j\)份糖果的方案数。
但是我们需要用前缀和优化转移方程。

#include <bits/stdc++.h>
#define endl '\n'

using namespace std;
using ll = long long;
constexpr int mod = 1e9 + 7;

ll dp[110][100010], pre[110][100010];
void solve() {
    int n, k; cin >> n >> k;
    vector<int> a(n + 1);
    ll sum = 0;
    for (int i = 1;i <= n;i++ ) {
        cin >> a[i];
        sum += 1ll * a[i];
    }
    if (sum < k && k != 0) {
        cout << 0 << endl;
        return;
    }

    for (int i = 0;i <= k;i++) pre[0][i] = 1;
    for (int i = 1;i <= n;i++) pre[i][0] = 1;

    for (int i = 1;i <= n;i++) {
        for (int j = 0;j <= k;j++) {
            int r = min(j, a[i]);
            dp[i][j] = pre[i - 1][j];
            if (j - r - 1 >= 0) dp[i][j] = (dp[i][j] - pre[i - 1][j - r - 1] + mod) % mod;
        }

        for (int j = 1;j <= k;j++) pre[i][j] = (dp[i][j] + pre[i][j - 1]) % mod;
    }
    cout << (dp[n][k] + mod) % mod << endl;
}
signed main(void) {
    cin.tie(nullptr), cout.tie(nullptr) -> ios::sync_with_stdio(false);
    solve(); 
    return 0;
}

M.Matching

状态压缩\(dp\)

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
constexpr int mod = 1e9 + 7;

int dp[23][1 << 23];
void solve() {
    int n; cin >> n;
    vector<int> have(n + 1);
    for (int i = 1;i <= n;i++) {
        for (int j = 0;j < n;j++) {
            int x; cin >> x;
            if (x == 1)  have[i] |= (1 << j);
        }
    }    
    dp[0][0] = 1;
    for (int i = 1;i <= n;i++) {
        for (int j = 0;j < (1 << n);j++) {
            if (__builtin_popcount(j) == i - 1) {
                for (int k = 0;k < n;k++) {
                    if ((have[i] >> k & 1) and (j >> k & 1) == 0) {
                        dp[i][j | (1 << k)] = (dp[i][j | (1 << k)] + dp[i - 1][j]) % mod;
                    }
                }
            }
        }
    }

    cout << dp[n][(1 << n) - 1] << '\n';
}

signed main(void) {
    cin.tie(nullptr), cout.tie(nullptr)->ios::sync_with_stdio(false);
    solve(); 
    return 0;
}

P.Independent Set

类似没有上司的舞会,树形\(dp\)

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
constexpr int mod = 1e9 + 7;

ll dp[100010][2];
void solve() {
    int n; cin >> n;
    vector<vector<int>> g(n + 1);
    for (int i = 0; i < n - 1;i++) {
        int u, v; cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    auto dfs = [&](auto self, auto &g, auto x, auto fa)->void {
        dp[x][0] = dp[x][1] = 1;
        for (auto y : g[x]) {
            if (y == fa) continue;
            self(self, g, y, x);
            dp[x][0] = dp[x][0] * (dp[y][0] + dp[y][1]) % mod;
            dp[x][1] = dp[x][1] * dp[y][0] % mod;  
        }
        return;
    };
    dfs(dfs, g, 1, -1);
    cout << (dp[1][0] + dp[1][1]) % mod << endl;
}

signed main(void) {
    cin.tie(nullptr), cout.tie(nullptr)->ios::sync_with_stdio(false);
    solve(); 
    return 0;
}

Q.Flowers

树状数组优化状态转移。
由于我们的\(dp\)值大小只会增大,所以可以用树状数组维护前缀最大值。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;
constexpr int mod = 1e9 + 7;
constexpr int N = 200010;
int n;
ll dp[200010], c[N];
void add(int x, ll d) {
    while ( x <= n) {
        c[x] = max(c[x], d);
        x += x & -x;
    }
}
ll query(int x) {
    ll res = 0;
    while (x > 0) {
        res = max(res, c[x]);
        x -= x & -x;
    }
    return res;
}
void solve() {
    cin >> n;
    vector<int> a(n + 1), h(n + 1);
    for (int i = 1;i <= n;i++) cin >> h[i];
    for (int i = 1;i <= n;i++) cin >> a[i];

    for (int i = 1;i <= n;i++) {
        ll res = query(h[i] - 1);
        dp[i] = max(dp[i], res + a[i]);
        add(h[i], dp[i]);
    }
    cout << *max_element(dp + 1, dp + n + 1) << endl;
    
}

signed main(void) {
    cin.tie(nullptr), cout.tie(nullptr)->ios::sync_with_stdio(false);
    solve(); 
    return 0;
}
posted @ 2022-10-17 17:29  Coldarra  阅读(38)  评论(0)    收藏  举报