在这片梦想之地,不堪回首的过去像泡沫一样散去,不愿|

PassName

园龄:3年1个月粉丝:32关注:16

2022 暑期 DP 极限单兵计划

前言

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

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

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

考虑根号分治

in :做多重背包,加前缀和优化

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

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

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

最后还要算上包括了体积为 n+1 的情况,即 g[i][j]+=g[i1][jn1]

#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 作为前 j1 层的点,剩余点作为第 j 层的点。
核心: 求出第 j 层的所有点到 S 的最短边,将这些边权和乘以 j,直接加到 f[S][j1] 上,即可求出 f[i][j]

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

f[i][j] 中花费最小的生成树一定可以被枚举到,因此 f[i][j]>=f[i][j]
如果第 j 层中用到的某条边 (a,b) 应该在比j小的层,假设 aS 中的点,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 的最优解

但是 jk 这两维都得开到 40000

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

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

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

对于在2窗口打饭的情况:dp[i][j]=min(dp[i][j],max(dp[i1][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

从放第一本书开始推,每次放两本相同高度的,设 fi,j 为左边放 i 本书,右边放 j 本书的方案

如果合法 , f[i][j]+=f[i2][j],f[i1][j1],f[i][j2],

邻接表正反都存一遍

得到的结果要除以 3 , 当放最后两本书时 , f[i2][j]=f[i1][j1]=f[i][j2]

#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 和第 2j 时选取了 k 个矩阵的答案。
3 种转移:第一列取一段,第二列取一段,两列一起取一个宽度为 2 的矩阵。

转移方程:

f[i][j][l]=max(f[i][j1][l],f[i1][j][l]);

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

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

f[i][j][l]=max(f[i][j][l],f[p][p][l1]+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[ic[j]][j]=1 (dp[i][j1]==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 位置,i1 是回来要跳的格子

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

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

转移方程为 ans=max(ans,dp[i]+s[min(n,i1+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][j1][0]+(len[j]+k)2
  • 考虑左边,在 j 右边枚举 p,且 color[j]==color[p] ,则为 ans=max(ans,dp(l,p,k+len[r])+dp(p+1,r1,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;
}

本文作者:PassName

本文链接:https://www.cnblogs.com/spaceswalker/p/16514433.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   PassName  阅读(121)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起