2022 暑期 DP 极限单兵计划

前言

LJ 认为我的 DP 是我的一大弱项,便精心为我准备了 毒瘤DP 12 题(然后发现原来给的 T1 是个树套树,就变成 毒瘤DP 11 题
感谢 LJ 教练。。。。。
为了方便复习,代码均格式化

T1 LibreOJ#6089. 小 Y 的背包计数问题

直接搞背包 dp 显然直接挂掉

考虑根号分治

\(i≤\sqrt{n}\) :做多重背包,加前缀和优化

\(i>\sqrt{n}\) :每种物品可以看作有无限个,做完全背包,但仍然是 \(O(n^2)\) 的。继续优化。

观察到物品个数至多 \(n−\sqrt{n}\) 个,设 \(g[i][j]\) 表示在 \(i\) 个物品体积为 \(j\) 的方案数。转移的话这里有一个技巧,即分两种特殊的情况,

第一种是 \(i\) 个物品的体积全部都大于 \(\sqrt{n}+1\),这样的话 \(g[i][j]+=g[i][j−i]\),可以看成前一种状态所有的物品的体积全部 \(+1\)

最后还要算上包括了体积为 \(\sqrt{n}+1\) 的情况,即 \(g[i][j]+=g[i−1][j−\sqrt{n}−1]\)

#include <bits/stdc++.h>

#define rint register int
#define endl '\n'

using namespace std;

const int N = 1e5 + 5;
const int M = 3e2 + 2e1;
const int mod = 23333333;

int n, m, f[M][N], g[M][N], sum[N], ans;

signed main() {
    cin >> n;
    m = sqrt(n);
    f[0][0] = 1;

    for (rint i = 1; i <= m; i++) {
        for (rint j = 0; j <= n; j++) {
            sum[j] = f[i - 1][j];
        }

        for (rint j = i; j <= n; j++) {
            sum[j] = (sum[j] + sum[j - i]) % mod;
        }

        for (rint j = 0; j <= n; j++) {
            f[i][j] = sum[j];

            if (j >= i * i + i) {
                f[i][j] = (f[i][j] - sum[j - i * i - i] + mod) % mod;
            }
        }
    }

    ans = f[m][n];

    g[0][0] = 1;

    for (rint i = 1; i <= m; i++) {
        for (rint j = i * m + i; j <= n; j++) {
            g[i][j] = (g[i][j - i] + g[i - 1][j - m - 1]) % mod;
            ans = (ans + (long long)g[i][j] * f[m][n - j] % mod) % mod;
        }
    }

    cout << ans << endl;

    return 0;
}

T2 P3959 [NOIP2017 提高组] 宝藏

状态压缩 \(DP\)

位二进制数,表示每个点是否存在。

状态 \(f[i][j]\) 表示:

集合:所有包含 \(i\) 中所有点,且树的高度等于 \(j\) 的生成树
属性:最小花费

状态计算:枚举 \(i\) 的所有非全集子集 \(S\) 作为前 \(j - 1\) 层的点,剩余点作为第 \(j\) 层的点。
核心: 求出第 \(j\) 层的所有点到 \(S\) 的最短边,将这些边权和乘以 \(j\),直接加到 \(f[S][j - 1]\) 上,即可求出 \(f[i][j]\)

证明:
将这样求出的结果记为 \(f'[i][j]\)

\(f[i][j]\) 中花费最小的生成树一定可以被枚举到,因此 \(f[i][j] >= f'[i][j]\)
如果第 \(j\) 层中用到的某条边 \((a, b)\) 应该在比j小的层,假设 \(a\)\(S\) 中的点,\(b\) 是第 \(j\) 层的点,则在枚举 \(S + {b}\) 时会得到更小的花费,即这种方式枚举到的所有花费均大于等于某个合法生成树的花费,因此 \(f[i][j] <= f'[i][j]\)

所以有 \(f[i][j] = f'[i][j]\)

#include <bits/stdc++.h>

#define rint register int
#define endl '\n'

using namespace std;

const int N = 1e1 + 2;
const int M = 5e3 + 5;

int n, m, f[M], g[N][N], d[N], ans = 0x3f3f3f3f;

int inline min(int a, int b) {
    return a < b ? a : b;
}

inline int read() {
    int x = 0, falg = 0;
    char c = getchar();

    while (c > '9' || c < '0') {
        if (c == '-')
            falg = 1;

        c = getchar();
    }

    while (c <= '9' && c >= '0') {
        x = x * 10 + c - '0';
        c = getchar();
    }

    return falg ? -x : x;
}

inline void write(int x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }

    if (x > 9)
        write(x / 10);

    putchar(x % 10 + '0');
}

void inline dfs(int u) {
    if (u == (1 << n) - 1) {
        return ;
    }

    for (rint i = 0; i < n; i++) {
        if (u >> i & 1) {
            for (rint j = 0; j < n; j++) {
                if (u >> j & 1) {
                    continue;
                }

                if (g[i][j] == 0x3f3f3f3f) {
                    continue;
                }

                int x = u | (1 << j);

                if (f[x] > f[u] + d[i]*g[i][j]) {
                    f[x] = f[u] + d[i] * g[i][j];
                    int t = d[j];
                    d[j] = d[i] + 1;
                    dfs(x);
                    d[j] = t; //恢复现场
                }
            }
        }
    }

    return ;
}

signed main() {
    n = read();
    m = read();

    memset(g, 0x3f, sizeof g);

    while (m--) {
        int a = read() - 1, b = read() - 1, c = read();
        g[a][b] = g[b][a] = min(g[a][b], c);
    }

    for (rint i = 0; i < n; i++) {
        memset(f, 0x3f, sizeof f);
        f[1 << i] = 0;

        for (rint j = 0; j < n; j++) {
            d[j] = n;
        }

        d[i] = 1;
        int p = 1 << i;
        int q = 1 << n;
        dfs(p);
        ans = min(ans, f[q - 1]);
    }

    write(ans);
    puts("");

    return 0;
}

T3 BZOJ#1996. [Hnoi2010]chorus

\(dp[i][j][0]\) : 第 \(i\) 人从左边进来的方案数

\(dp[i][j][1]\) : 第 \(j\) 人从右边进来的方案数

剩下的看代码就可以了,这个题比较水

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int mod = 19650827ll;
const int N = 5e3 + 5;
const int M = 1e1 - 7;

int n, a[N], dp[N][N][M];

signed main() {
    cin >> n;

    for (rint i = 1; i <= n; i++) {
        cin >> a[i];
    }

    for (rint i = 1; i <= n; i++)
        dp[i][i][0] = 1;

    for (rint len = 2; len <= n; len++)
        for (rint l = 1; l <= n; l++) {
            int r = l + len - 1;

            if (a[l] < a[l + 1])
                dp[l][r][0] += dp[l + 1][r][0];

            if (a[l] < a[r])
                dp[l][r][0] += dp[l + 1][r][1];

            if (a[r] > a[l])
                dp[l][r][1] += dp[l][r - 1][0];

            if (a[r] > a[r - 1])
                dp[l][r][1] += dp[l][r - 1][1];

            dp[l][r][0] %= mod, dp[l][r][1] %= mod;
        }

    cout << (dp[1][n][0] + dp[1][n][1]) % mod);

    return 0;
}

T4 P2577 [ZJOI2004]午餐

\(f[i][j][k]\) 表示前 \(i\) 个人在 \(1\) 窗口打饭时间为 \(j\),在 2 窗口打饭为 \(k\) 的最优解

但是 \(j\)\(k\) 这两维都得开到 \(40000\)

考虑优化空间。发现只要对打饭时间维护一个前缀和,那么就可以只保存一个时间了,另外一个时间则是 \(sum[i]−j\)

考虑转移(方程里面的 \(s[i]\) 是前缀和)

对于在1窗口打饭的情况:\(dp[i][j]=min(dp[i][j],max(dp[i−1][j−a[i].x],j+a[i].y))\)

对于在2窗口打饭的情况:\(dp[i][j]=min(dp[i][j],max(dp[i−1][j],s[i]−j+a[i].y))\)

那么最优解就是在 \(dp[n][1...s[n]]\)里面找了

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 3e2 + 5;
const int M = 4e4 + 5;

struct S {
    int x, y;
} a[N];
int h1, h2, n, s[N], dp[N][M];

bool inline cmp(S a, S b) {
    return a.y > b.y;
}

int inline min(int a, int b) {
    return a < b ? a : b;
}

int inline max(int a, int b) {
    return a > b ? a : b;
}

signed main() {
    cin >> n;

    for (rint i = 1; i <= n; i++)
        cin >> a[i].x >> a[i].y;

    memset(dp, 0x3f, sizeof dp);
    sort(a + 1, a + n + 1, cmp);

    for (rint i = 1; i <= n; i++) {
        s[i] = s[i - 1] + a[i].x;
    }

    dp[0][0] = 0;

    for (rint i = 1; i <= n; i++) {
        for (rint j = 0; j <= s[i]; j++) {
            dp[i][j] = min(dp[i][j], max(dp[i - 1][j], s[i] - j + a[i].y));

            if (j - a[i].x >= 0)
                dp[i][j] = min(dp[i][j], max(dp[i - 1][j - a[i].x], j + a[i].y));
        }
    }

    int ans = 0x3f3f3f3f;

    for (int i = 0; i <= s[n]; i++) {
        ans = min(ans, dp[n][i]);
    }

    cout << ans << endl;

    return 0;
}

T5 51NOD#.1522 上下序列

区间 dp

从放第一本书开始推,每次放两本相同高度的,设 $ f_{i,j}$ 为左边放 \(i\) 本书,右边放 \(j\) 本书的方案

如果合法 , \(f[i][j]+=f[i-2][j],f[i-1][j-1],f[i][j-2]\),

邻接表正反都存一遍

得到的结果要除以 \(3\) , 当放最后两本书时 , \(f[i-2][j]=f[i-1][j-1]=f[i][j-2]\)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 4e2 + 5;
const int M = 8e2 + 5;

int n, f[N][N], idx, k, ans, h[N], ne[M], w[M], e[M];

void add(int a, int b, int c) {
    ne[++idx] = h[a], w[idx] = b, e[idx] = c, h[a] = idx;
}
//建立邻接表

bool spfa(int l, int r, int ll, int rr) {
//寻找出边;
//l表示放的两本书中左边的那本,r是右边那本
//ll表示没有放书的区间的左端点,rr表示右端点
//可知 ll~rr 中的书高度都大于将要放的两本书
    for (rint i = h[l]; i; i = ne[i]) {
        int y = w[i];
        int z = e[i];

        if (y == r) {
            if (z == 2 || z == 4) {
                return 0;
            }
        }//等于的情况只可能:出边为放的另一本书的位置

        else if (z <= 2) {
            if (y < ll || y > rr) {
                return 0;
            }
        }

        else if (z >= 4) {
            if (y > ll && y < rr) {
                return 0;
            }
        }

        else if (z == 3) {
            return 0;
        }
    }//寻找与左端点有关的条件

    for (rint i = h[r]; i; i = ne[i]) {
        int y = w[i];
        int z = e[i];

        if (y == l) {
            if (z == 2 || z == 4) {
                return 0;
            }
        }

        else if (z <= 2) {
            if (y < ll || y > rr) {
                return 0;
            }
        }

        else if (z >= 4) {
            if (y >= ll && y <= rr) {
                return 0;
            }
        }

        else if (z == 3) {
            return 0;
        }
    }//寻找与右端点有关的条件

    return 1;
}

signed main() {
    cin >> n >> k;

    for (rint i = 1; i <= k; i++) {
        int x, y;
        int z = 0;
        string c;

        cin >> x >> c >> y;

        if (c[1] == '=') {
            if (c[0] == '<') {
                z = 1;
            } else {
                z = 5;
            }
        }

        else {
            if (c[0] == '=') {
                z = 3;
            }

            if (c[0] == '<') {
                z = 2;
            }

            if (c[0] == '>') {
                z = 4;
            }
        }

        if (x == y) {
            if (z == 2 || z == 4) {
                puts("0");
                exit(0);
            } else {
                continue;
            }
        }

        add(x, y, z);
        add(y, x, 6 - z);
    }

    f[0][0] = 1;

    for (rint i = 1; i <= n; i++) {
        for (rint j = 0; j <= 2 * i; j++) {

            if (j >= 2 && (k == 0 || spfa(j - 1, j, j + 1, 2 * n - 2 * i + j))) {
                f[j][2 * i - j] += f[j - 2][2 * i - j];
            }

            if (j >= 1 && 2 * i - j >= 1 && (k == 0 || spfa(j, 2 * n - 2 * i + j + 1, j + 1, 2 * n - 2 * i + j))) {
                f[j][2 * i - j] += f[j - 1][2 * i - j - 1];
            }

            if (2 * i - j >= 2 && (k == 0 ||
                                   spfa(2 * n - 2 * i + j + 1, 2 * n - 2 * i + j + 2, j + 1, 2 * n - 2 * i + j))) {
                f[j][2 * i - j] += f[j][2 * i - j - 2];
            }
        }
    }

    for (rint i = 0; i <= 2 * n; i++) {
        ans += f[i][2 * n - i];
    }

    cout << ans / 3 << endl;

    return 0;
}

T6 BZOJ#1084. [SCOI2005]最大子矩阵

\(m=1\) ,题目等于求的是一段,直接计算最大 \(m\) 子段和。

\(m=2\) , \(f[i][j][k]\) 表示扫描到第一列 \(i\) 和第 \(2\)\(j\) 时选取了 \(k\) 个矩阵的答案。
\(3\) 种转移:第一列取一段,第二列取一段,两列一起取一个宽度为 \(2\) 的矩阵。

转移方程:

\[f[i][j][l]=max(f[i][j-1][l],f[i-1][j][l]); \]

\[f[i][j][l]=max(f[i][j][l],f[p][j][l-1]+s[1][i]-s[1][p]); \]

\[f[i][j][l]=max(f[i][j][l],f[i][p][l-1]+s[2][j]-s[2][p]); \]

\[f[i][j][l]=max(f[i][j][l],f[p][p][l-1]+s[1][i]-s[1][p]+s[2][i]-s[2][p]); \]

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 1e2 + 5;
const int M = 1e1 - 5;

int n, m, k, dp[N][M * 3], dp2[N][N][M * 3], a[N][M * 2], s[M][N];

int inline max(int a, int b) {
    return a > b ? a : b;
}

signed main() {
    cin >> n >> m >> k;

    for (rint i = 1; i <= n; i++) {
        for (rint j = 1; j <= m; j++) {
            cin >> a[i][j];
        }

        s[1][i] = s[1][i - 1] + a[i][1];
        s[2][i] = s[2][i - 1] + a[i][2];
    }

    if (m == 1) {
        memset(dp, -0x3f, sizeof dp);

        for (rint i = 0; i <= n; i++) {
            dp[i][0] = 0;
        }

        for (rint i = 1; i <= n; i++) {
            for (rint j = 1; j <= k; j++) {
                dp[i][j] = dp[i - 1][j];

                for (rint p = 0; p <= i - 1; p++) {
                    dp[i][j] = max(dp[i][j], dp[p][j - 1] + s[1][i] - s[1][p]);
                }
            }
        }

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

        return 0;
    }

    memset(dp2, -0x3f, sizeof dp2);

    for (rint i = 0; i <= n; i++) {
        for (rint j = 0; j <= n; j++) {
            dp2[i][j][0] = 0;
        }
    }

    for (rint i = 1; i <= n; i++) {
        for (rint j = 1; j <= n; j++) {
            for (rint l = 1; l <= k; l++) {
                dp2[i][j][l] = max(dp2[i][j - 1][l], dp2[i - 1][j][l]);

                for (rint p = 0; p <= i - 1; p++) {
                    dp2[i][j][l] = max(dp2[i][j][l], dp2[p][j][l - 1] + s[1][i] - s[1][p]);
                }

                for (rint p = 0; p <= j - 1; p++) {
                    dp2[i][j][l] = max(dp2[i][j][l], dp2[i][p][l - 1] + s[2][j] - s[2][p]);
                }

                if (i == j) {
                    for (rint p = 0; p <= i - 1; p++) {
                        dp2[i][j][l] = max(dp2[i][j][l], dp2[p][p][l - 1] + s[1][i] - s[1][p] + s[2][i] - s[2][p]);
                    }
                }
            }
        }
    }

    cout << dp2[n][n][k] << endl;

    return 0;
}

T7 BZOJ#2748. [HAOI2012]音量调节

背包 dp

定义状态 \(dp[i][j]\) 表示到了第 \(j\) 次操作,能否达到 \(i\) 的音量。初始状态 \(dp[beginLevel][0]=1\)

状态转移方程:$dp[i+c[j]][j]=dp[i−c[j]][j]=1 $ $ $ $ $ $ $ \((dp[i][j−1]==1)\)

然后倒序找满足 \(dp[i][n]=1\) 的最大的 \(i\)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 2e3 + 5;
const int M = 5e1 + 5;

int n, s, e, c[N], dp[N][M];
//s 为 beginLevel ; e 为 maxlevel。

signed main() {
    cin >> n >> s >> e;

    for (rint i = 1; i <= n; i++) {
        cin >> c[i];
    }

    dp[s][0] = 1;

    for (rint i = 1; i <= n; i++) {
        for (rint j = 0; j <= e; j++) {
            if (dp[j][i - 1] != 0) {
                int p = j - c[i];
                int q = j + c[i];

                if (p >= 0) {
                    dp[p][i] = 1;
                }

                if (q <= e) {
                    dp[q][i] = 1;
                }
            }
        }
    }

    for (rint i = e; i >= 0; i--) {
        if (dp[i][n] != 0) {
            cout << i;
            return 0;
        }
    }

    cout << -1 << endl;

    return 0;
}

T8 BZOJ#1915. [USACO2010]奶牛的跳格子游戏

\(dp[i]\) 表示跳到 \(i\) 位置,\(i-1\) 是回来要跳的格子

格子肯定都是正数,我们不可能去跳负数,所以前缀和不能按照正常的情况计算,要分类讨论

使用单调队列来优化, 单调队列中比较 \(dp[j] - sum[j]\) 即可

转移方程为 \(ans = max(ans, dp[i] + s[min(n, i - 1 + m)] - s[i])\)

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 3e5 + 5;

int ans, n, m, dp[N], s[N], a[N], q[N];

int inline min(int a, int b) {
    return a < b ? a : b;
}

int inline max(int a, int b) {
    return a > b ? a : b;
}

signed main() {
    cin >> n >> m;

    for (rint i = 1; i <= n; i++) {
        cin >> a[i];

        if (a[i] > 0) {
            s[i] = s[i - 1] + a[i];
        } else {
            s[i] = s[i - 1];
        }
    }

    int p = min(n, m);

    ans = s[p];

    int h = 0;
    int t = 0;

    q[t++] = 1;
    dp[1] = a[1];

    for (rint i = 2, j = 0; i <= n; i++, j++) {
        while (h < t && i - q[h] > m) {
            h++;
        }

        int val = dp[j] - s[j];

        while (h < t && val >= dp[q[t - 1]] - s[q[t - 1]]) {
            t--;
        }

        q[t++] = j;
        int x = q[h];

        dp[i] = dp[x] + a[i] + a[i - 1] + s[i - 2] - s[x];
    }

    for (rint i = 1; i <= n; i++) {
        ans = max(ans, dp[i] + s[min(n, i - 1 + m)] - s[i]);
    }

    cout << ans << endl;

    return 0;
}

T9 P1131 [ZJOI2007] 时态同步

树形 dp

\(dp[i]\) 记录以 \(i\) 为根时的最大时间

首先一个边的循环递归,找出每个节点的最大时间,再进行一次循环,每一次 \(ans\) 加上当前节点的最大时间减去子节点加上边权,最后得出答案即可。

\[dp[x]=max(dp[x],dp[e[i]]+w[i]); \]

\[ans+=dp[x]-dp[e[i]]-w[i]; \]

#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 1e6 + 5;

int n, s, h[N], e[N], ne[N], w[N], idx, ans, maxn, p[N], tot;
bool v[N];

int inline max(int a, int b) {
    return a > b ? a : b;
}

void add(int a, int b, int c) {
    e[++idx] = b, ne[idx] = h[a], h[a] = idx, w[idx] = c;
}

void init(int x) {
    v[x] = true;

    for (rint i = h[x]; i; i = ne[i]) {
        if (!v[e[i]]) {
            init(e[i]);
            p[x] = max(p[x], p[e[i]] + w[i]);
        }
    }

    return ;
}

void dfs(int x) {
    v[x] = false;

    for (rint i = h[x]; i; i = ne[i]) {
        if (v[e[i]]) {
            dfs(e[i]);
            ans += p[x] - p[e[i]] - w[i];
        }
    }

    return ;
}

signed main() {
    cin >> n >> s;

    for (rint i = 1; i < n; i++) {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
        add(y, x, z);
    }

    init(s);
    dfs(s);

    cout << ans << endl;

    return 0;
}

T10 UVA10559 方块消除

区间 dp + dfs

读入时预处理,将各个连续同色区间处理为一个点,记录颜色和长度

对于一个连续的同色区间,可以直接消掉,或者从左边或者右边搞到和它同色的区间和在一起再一起消掉

\(dp[i][j][k]\) 表示区间 \(i\) 到区间 \(j\) 且在区间 \(j\) 右边添加 \(k\) 个格子的最大分数

  • 直接消除,即左边不考虑,为 \(dp[i][j-1][0]+(len[j]+k)^2\)
  • 考虑左边,在 \(j\) 右边枚举 \(p\),且 \(color[j]==color[p]\) ,则为 \(ans=max(ans,dp(l,p,k+len[r])+dp(p+1,r-1,0))\)
#include <bits/stdc++.h>

#define rint register int
#define int long long
#define endl '\n'

using namespace std;

const int N = 2e2 + 5;

int c[N], dp[N][N][N], idx, len[N];
//c 表示颜色

int inline dfs(int l, int r, int k) {
    int &res = dp[l][r][k];

    if (res > -1) {
        return res;
    }

    res = dfs(l, r - 1, 0) + (len[r] + k) * (len[r] + k);

    for (rint p = l; p < r; p++) {
        if (c[p] != c[r]) {
            continue;
        }

        res = max(res, dfs(l, p, k + len[r]) + dfs(p + 1, r - 1, 0));
    }

    return res;
}

signed main() {
    int T;
    cin >> T;
    int T1 = T;

    while (T--) {
        memset(dp, -1, sizeof dp);
        memset(len, 0, sizeof len);

        int L, a, l = 1;
        cin >> L >> a;

        idx = 0;

        for (rint j = 2, b; j <= L; j++) {
            cin >> b;

            if (b == a) {
                l++;
            } else {
                c[++idx] = a;
                len[idx] = l;
                l = 1;
                a = b;
            }
        }

        c[++idx] = a;
        len[idx] = l;

        for (rint j = 1; j <= idx; j++) {
            dp[j][j][0] = (len[j]) * (len[j]);
            dp[j][j - 1][0] = 0;
        }

        printf("Case %lld: %lld", T1 - T, dfs(1, idx, 0));

        puts("");
    }

    return 0;
}
posted @ 2022-07-24 14:05  PassName  阅读(117)  评论(0编辑  收藏  举报