毒瘤 dp 题做题记录

子序列

题目链接

Solution

(f_{i,j}) 表示以 (i) 结尾的一组选择了 (j) 个数,剩下的也能凑出一组时,剩下一组结尾数的最小值。

(a_{i+1}>a_i) 时,(i)(i+1) 可以分到一组,因此 (f_{i+1,j}=max(f_{i+1,j},f_{i,j}))

(a_{i+1}>f_{i,j}) 时,(i+1) 能分到剩下的一组,该组原有 (j-i) 个数,因此 (f_{i+1,j}=max(f_{i+1,j},a_i))

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
const int N = 2333;
int n, a[N], f[N][N];
 
int main()
{
    while(cin >> n)
    {
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
        memset(f, 0x3f, sizeof(f));
        f[1][1] = 0;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= i; j++)
                if(f[i][j] != 0x3f3f3f3f)
                {
                    if(a[i] < a[i + 1])
                        f[i + 1][j + 1] = min(f[i + 1][j + 1], f[i][j]);
                    if(a[i + 1] > f[i][j])
                        f[i + 1][i - j + 1] = min(f[i + 1][i - j + 1], a[i]);
                }
        if(f[n][n / 2] != 0x3f3f3f3f) printf("Yes!
");
        else printf("No!
");
    }
    return 0;
}

题目链接

Solution

(f_{i,j}) 表示前 (i) 个积木搭成的左塔和右塔差为 (j) 时,左边的塔的最大高度

(1.) 不放如当前积木

(f_{i,j}=max(f_{i,j},f_{i-1,j}))

(2.) 当前积木放在左边

(f_{i,j}=max(f_{i,j},f_{i-1,j-a_i}+a_i))

(3.) 当前积木放在右边

(f_{i,j}=max(f_{i,j},f_{i-1,j+a_i}))

注意:

(1.) 差值可能为负数,根据数据范围,(j) 直接加上 (500000)

(2.) 注意内存,使用滚动数组优化

Code

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
 
const int p = 500000;
int n, Ans = 0, a[51], f[2][1000001];
 
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    memset(f, -0x3f, sizeof(f));
    f[0][p] = 0;
    for(int i = 1; i <= n; i++)
        for(int j = 0; j <= 1000000; j++)
        {
            f[i & 1][j] = max(f[i & 1][j], f[(i & 1) ^ 1][j]);
            if(j - a[i] >= 0)
                f[i & 1][j] = max(f[i & 1][j], f[(i & 1) ^ 1][j - a[i]] + a[i]);
            if(j + a[i] <= (p << 1))
                f[i & 1][j] = max(f[i & 1][j], f[(i & 1) ^ 1][j + a[i]]);
            if(j == p) Ans = max(Ans, f[i & 1][p]);
        }
    if(Ans != 0) printf("%d", Ans);
    else printf("-1");
    return 0;
}

CSP2019 括号树

题目链接

Solution

历年真题,WA 到生活不能自理

首先考虑链的情况,若当前走到 (x) ((s_x==')')),计算以当前位置为结尾的合法括号串个数记为 (f_x)

首先不考虑多个合法括号串并列的情况,这样的括号串有且仅有一个。用栈维护与其配对的前括号 (y)(x)(y) 之间组成一个单独的以 (x) 为结尾的合法括号串 (S),因此 (f_x++);若考虑并列情况,每一个以 (y-1) 为结尾的合法括号串都可以与 (S) 并列形成一个新的合法括号串,(f_x+=f_{fa_y})。综上 (f_x=f_{fa_y}+1)

(sum_x) 表示走到 (x) 时所有的合法括号串,则 (sum_x=sum_{fa_x}+f_x)。树上的情况也大致相同,一定要注意回溯的写法(非这样写不可,否则会 WA 到起飞)

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
const int N = 2333333;
struct edge { long long nxt, to; } e[N];
long long n, Ans = 0, st[N], sum[N], head[N], f[N], fa[N], top = 0, cnt = 0;
char s[N];
 
void add(long long x, long long y)
{
    e[++cnt] = (edge) { head[x], y };
    head[x] = cnt;
}
 
void dfs(long long x)
{
    long long y = 0;
    if(s[x] == '(') st[++top] = x;
    else if(top)
    {
        y = st[top--];
        f[x] = f[fa[y]] + 1;
    }
    sum[x] = sum[fa[x]] + f[x];
    Ans ^= sum[x] * x;
    for(long long i = head[x]; i; i = e[i].nxt) dfs(e[i].to);
    if(s[x] == '(') top--;
    else if(y) st[++top] = y;
}
 
int main()
{
    scanf("%lld", &n);
    memset(sum, 0, sizeof(sum));
    memset(f, 0, sizeof(f));
    memset(head, 0, sizeof(head));
    for(long long i = 1; i <= n; i++) cin >> s[i];
    for(long long i = 2; i <= n; i++)
    {
        cin >> fa[i];
        add(fa[i], i);
    }
    dfs(1);
    printf("%lld", Ans);
    return 0;
}

一本通高手训练 软件开发

题目链接

Solution

不想写了抄题解罢...(其实是写不好)

考虑二分答案 (mid),原问题变为判定是否存在一种方案,在给定天数内使得两个软件都能至少被完成 (m) 个模块。

我们设 (f_{i,j}) 表示已经处理到第 (i) 个技术人员,第一个软件完成 (j) 个模块时第二个软件最多能完成多少个模块,则状态转移方程为:

(f_{i,j}=max(f_{i-1,j-k}+frac{mid-k*d1_i}{d2_i}))

表示第 (i) 个技术人员第一个软件完成 (k) 个模块,则第二个软件最多完成 (frac{mid-k*d1_i}{d2_i}) 个模块。

最后若 (f_{n,m}>=m) 则答案合法,时间复杂度为 (O(nm^2log_{nm}))

注意边界!

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
 
const int N = 1100;
int n, m, Ans, l = 0, r = 0, d1[N], d2[N], f[N][N];
 
bool check(int x)
{
    memset(f, -0x3f, sizeof(f));
    f[0][0] = 0;
    for(int i = 1; i <= n; i++) 
        for(int j = 0; j <= m; j++)
            for(int k = 1; k <= j && k * d1[i] <= x; k++)
                f[i][j] = max(f[i][j], f[i - 1][j - k] + (x - k * d1[i]) / d2[i]);
    if(f[n][m] >= m) return true;
    return false;
}
 
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
    {
        scanf("%d%d", &d1[i], &d2[i]);
        r += d1[i] * m + d2[i] * m;
    }
    while(l + 1 < r)
    {
        int mid = (l + r) >> 1;
        if(check(mid)) r = mid;
        else l = mid;
    }
    if(check(l)) Ans = l;
    else Ans = r;
    printf("%d", Ans);
    return 0;
}
原文地址:https://www.cnblogs.com/Andy-park/p/13556321.html