暑假集训week3-DP

暑假集训-week3-动态规划

A - 最大子段和

在这里插入图片描述
经典的最大子段和

#include <bits/stdc++.h>
using namespace std;
// 最大子段和(dp模板题)
// https://www.luogu.com.cn/problem/P1115
int a[200001];
int dp[200001];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    int ans = -9999999;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        dp[i] = max(dp[i - 1] + a[i], a[i]);
        ans = max(dp[i], ans);
    }
    cout << ans;
    return 0;
}

B - Max Sum Plus Plus

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
// https://vjudge.csgrandeur.cn/contest/507882#problem/B
// 参考题解:https://blog.csdn.net/weixin_44035017/article/details/103318078?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165934103416782388023006%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165934103416782388023006&biz_id=0&spm=1018.2226.3001.4187
// 二维dp+滚动数组优化
const int maxn = 1e6 + 9;
int n, m, a[maxn], dp[maxn], lastmax[maxn];
int remp_sum, sum;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    while (scanf("%d%d", &m, &n) != EOF)
    {
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        memset(dp, 0, sizeof(dp));
        memset(lastmax, 0, sizeof(lastmax));
        remp_sum = 0, sum = 0;
        for (int i = 1; i <= m; i++)
        {
            sum = -0x3f3f3f3f;
            for (int j = i; j <= n; j++)
            {
                dp[j] = max(dp[j - 1] + a[j], lastmax[j - 1] + a[j]);
                lastmax[j - 1] = sum;
                sum = max(sum, dp[j]);
            }
        }
        cout << sum << endl;
    }
    return 0;
}

C - Longest Ordered Subsequence

在这里插入图片描述
最大上升子序列模板

#include <iostream>
using namespace std;
// 模板:最大上升子序列
const int maxn = 100010, INF = 0x7f7f7f7f;
int a[maxn], dp[maxn]; // dp[i]代表以a[i]结尾的子序列最大长度
int n, ans = -INF;
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        dp[i] = 1;
    }
    for (int i = 1; i <= n; i++)
        for (int j = 1; j < i; j++)
            if (a[j] < a[i])
                dp[i] = max(dp[i], dp[j] + 1);
    for (int i = 1; i <= n; i++)
        ans = max(ans, dp[i]);
    printf("%d\n", ans);
    return 0;
}

D - 采药

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
// 01背包模板题
const int maxn = 10000;
int t, m;
int cost[maxn], value[maxn], bag[maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> t >> m;
    for (int i = 1; i <= m; i++)
    {
        cin >> cost[i] >> value[i];
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = t; j >= cost[i]; j--)
        {
            bag[j] = max(bag[j - cost[i]] + value[i], bag[j]);
        }
    }
    cout << bag[t];
    return 0;
}

E - Piggy-Bank

在这里插入图片描述
在这里插入图片描述
完全背包分别求最大和求最小
求最小时先全统一赋值为无穷大,然后再将初始赋值为0
顺便快读的板子也在这里了

#include <bits/stdc++.h>
using namespace std;
// 传送门:https://vjudge.csgrandeur.cn/contest/507882#problem/E
// 完全背包
// 快读不能和清缓存一起用!
const int maxn = 100100;
int t, e, f, n, m; // n为个数,m为背包大小
int cost[maxn], value[maxn], bag[maxn];
inline int read()
{
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9')
    {
        if (ch == '-')
            w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9')
        s = s * 10 + ch - '0', ch = getchar();
    return s * w;
}
int main()
{
    t = read();
    while (t--)
    {
        memset(bag, 0x3f, sizeof(bag));
        e = read(), f = read(), n = read();
        m = f - e;
        for (int i = 1; i <= n; i++)
        {
            value[i] = read();
            cost[i] = read();
        }
        bag[0] = 0;
        for (int i = 1; i <= n; i++)
        {
            int p = value[i];
            int w = cost[i];
            for (int j = w; j <= m; j++)
            {
                bag[j] = min(p + bag[j - w], bag[j]);
            }
        }
        if (bag[m] == 0x3f3f3f3f)
            cout << "This is impossible." << endl;
        else
            printf("The minimum amount of money in the piggy-bank is %d.\n", bag[m]);
    }

    return 0;
}

F - Dividing

在这里插入图片描述
在这里插入图片描述
多重背包的二进制优化

#include <bits/stdc++.h>
using namespace std;
// https://vjudge.csgrandeur.cn/contest/507882#problem/F
// 多重背包优化
// 二进制优化
int cnt[601000];
int sum;
int value[601000]; //此题不需要空间
int dp[601000];
inline int read()
{
    sum = 0;
    for (int i = 1; i <= 6; i++)
        cin >> cnt[i], sum += cnt[i] * i;
    return sum;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    int flag = 0;
    while (read())
    {
        memset(dp, 0, sizeof(dp));
        memset(value, 0, sizeof(value));
        printf("Collection #%d:\n", ++flag);
        if (sum % 2)
        {
            printf("Can't be divided.\n\n");
            continue;
        }
        int num = 0;
        for (int i = 1; i <= 6; i++)
        {
            int remp = cnt[i];
            int k = 1; //二进制个数
            while (k <= remp)
            {
                num++;
                value[num] = i * k;
                remp -= k;
                k *= 2;
            }
            if (remp > 0)
            {
                num++;
                value[num] = i * remp;
            }
        }
        sum /= 2;
        for (int i = 1; i <= num; i++)
            for (int j = sum; j >= value[i]; j--)
                dp[j] = max(dp[j], dp[j - value[i]] + value[i]); //注意传进max内的两个参数
        if (dp[sum] == sum)
            printf("Can be divided.\n\n");
        else
            printf("Can't be divided.\n\n");
    }
    return 0;
}

G - 石子合并

在这里插入图片描述
一个环形的链!!!
处理方法很有意思,直接将原链扩充二倍,在长度2*n的直链取一个长度n的链得到最大贡献值

#include <bits/stdc++.h>
using namespace std;
// 区间dp:https://www.luogu.com.cn/problem/P1880
// 因为是一个环形区间,我们可以通过将数组扩充两倍来实现
const int maxn = 330;
int n;
int a[maxn], sum[maxn], dp_min[maxn][maxn], dp_max[maxn][maxn];
inline int d(int i, int j)
{
    return sum[j] - sum[i - 1];
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    memset(dp_min, 0x3f3f3f3f, sizeof(dp_min));
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    for (int i = 1; i <= 2 * n; i++)
    {
        a[i + n] = a[i];
        sum[i] = a[i] + sum[i - 1];
        dp_min[i][i] = 0;
    }
    for (int len = 1; len < n; len++)
    {
        for (int l = 1, r = l + len; (r <= n + n) && (l <= n + n); l++, r = l + len)
        {
            for (int k = l; k < r; k++) //这个地方是小于号!!!加了等于的话就超出边界了!
            {
                dp_max[l][r] = max(dp_max[l][r], dp_max[l][k] + dp_max[k + 1][r] + d(l, r));
                dp_min[l][r] = min(dp_min[l][r], dp_min[l][k] + dp_min[k + 1][r] + d(l, r));
            }
        }
    }
    int max1 = -0x3f3f3f3f, min1 = 0x3f3f3f3f;
    for (int i = 1; i <= n; i++)
    {
        max1 = max(max1, dp_max[i][i + n - 1]);
        min1 = min(min1, dp_min[i][i + n - 1]);
    }
    cout << min1 << endl
         << max1 << endl;
    return 0;
}

H - 能量项链

在这里插入图片描述
首尾的处理很有意思
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
// 区间dp
// https://www.luogu.com.cn/problem/P1063
const int maxn = 3 * 110;
int n;
int head[maxn], tail[maxn];
int dp[maxn][maxn];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> head[i];
        head[i + n] = head[i];
    }
    for (int i = 1; i <= 2 * n - 1; i++)
    {
        tail[i] = head[i + 1];
    }
    tail[2 * n] = head[1];
    for (int len = 2; len <= n; len++)
    {
        for (int l = 1, r = l + len - 1; (l <= 2 * n) && (r <= 2 * n); l++, r = l + len - 1)
        {
            for (int k = l; k < r; k++)
                dp[l][r] = max(dp[l][r], dp[l][k] + dp[k + 1][r] + head[l] * tail[k] * tail[r]);
        }
    }
    int res = 0;
    for (int i = 1; i <= n; i++)
    {
        res = max(res, dp[i][i + n -1]);
    }
    cout << res;
    return 0;
}

I - 没有上司的舞会

在这里插入图片描述
在这里插入图片描述
树形dp模板题,看注释
感觉树形dp的思路还是很清晰的

#include <bits/stdc++.h>
using namespace std;
int n;
int happy[6010], dp[6010][2];
// dp[x][1]:代表x节点参选,此时以x为根的子树贡献最大和
// dp[x][0]:代表x节点不参选,此时以x为根的子树贡献最大和
// 传送门:https://www.luogu.com.cn/problem/P1352
bool visit[6010];
vector<int> son[6010];
void search(int x) //搜索
{
    int fa = x;
    dp[x][1] = happy[x], dp[x][0] = 0;
    for (int i = 0; i < son[x].size(); i++)
    {
        int s = son[x][i];
        search(s);
        dp[x][1] += dp[s][0];
        dp[x][0] += max(dp[s][0], dp[s][1]);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> happy[i];
    }
    for (int i = 1; i <= n - 1; i++)
    {
        int l, k;
        cin >> l >> k;
        visit[l] = true; //他有父亲节点
        son[k].push_back(l);
    }
    int root; //找到根节点
    for (int i = 1; i <= n; i++)
        if (visit[i] == false)
        {
            root = i;
            break;
        }
    search(root);
    cout << max(dp[root][1], dp[root][0]);
    return 0;
}

J - 战略游戏

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
// https://www.luogu.com.cn/problem/P2016
// 树形dp
const int maxn = 200000;
int n, dp[maxn][2];
vector<int> son[maxn];
int cnt;
bool visit[maxn];
void search(int x)
{
    dp[x][1] = 1, dp[x][0] = 0;
    int f = x;
    for (int i = 0; i < son[f].size(); i++)
    {
        int s = son[f][i];
        search(s);
        //若该点无士兵,则子节点必须全部都有士兵占据
        dp[f][0] += dp[s][1];
        // 若该点没有士兵,子节点有无士兵都可,只要最小就行
        dp[f][1] += min(dp[s][1], dp[s][0]);
    }
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        int k, tar;
        cin >> tar >> k;
        for (int j = 1; j <= k; j++)
        {
            int remp;
            cin >> remp;
            visit[remp] = true;
            son[tar].push_back(remp);
        }
    }
    int ans = 0, root = 0;
    for (int i = 0; i < n; i++)
        if (visit[i] == false)
        {
            root = i;
            break;
        }
    search(root);
    cout << min(dp[root][0], dp[root][1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值