本周写题报告

洛谷 P1616 疯狂的采药

思路:

这题是一个标准的完全背包问题,在分析的时候我们就把题目中的背景去掉,体积就是时间,这样更有利于分析此题以及联想其他背包题目

不难得出递推式:dp[j] = max(dp[j - h[i]] + w[i], dp[j]);

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
typedef long long ll;
int h[N], w[N];
const int M = 1e7 + 10;
ll dp[M];
int main()
{
    int t, m;
    scanf("%d%d", &t, &m);
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d", &h[i], &w[i]);
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = h[i]; j <= t; j++)
        {
            dp[j] = max(dp[j - h[i]] + w[i], dp[j]);
        }
    }
    printf("%lld\n", dp[t]);
    return 0;
}

洛谷 P1833 樱花

思路:这题是一道混合背包题,判断是完全,01,或是多重,再选择

状态转移方程:dp[j] = max(dp[j - t[i]] + w[i], dp[j]);

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int t[N], w[N], p[N];
int dp[1008];
int main()
{
    int h1, h2, m1, m2;
    scanf("%d:%d %d:%d", &h1, &m1, &h2, &m2);
    int n;
    scanf("%d", &n);
    int ans = (m2 - m1) + (h2 - h1) * 60;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d%d%d", &t[i], &w[i], &p[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        if (p[i] == 0) //完全背包
        {
            for (int j = t[i]; j <= ans; j++)
            {
                dp[j] = max(dp[j - t[i]] + w[i], dp[j]);
            }
        }
        else //多重背包和01背包
        {
            for (int k = 1; k <= p[i]; k++)
            {
                for (int j = ans; j >= k * t[i]; j--)
                {
                    dp[j] = max(dp[j - t[i]] + w[i], dp[j]);
                }
            }
        }
    }
    printf("%d\n", dp[ans]);
    return 0;
}

洛谷 P1855 榨取kkksc03

思路:这题是一个经典的二维背包题,就是在01背包的基础上加上一层循环即可

状态转移方程:dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + 1);

#include <bits/stdc++.h>
using namespace std;
int a[108], b[108];
int dp[300][300];
int main()
{
    int n, m, t;
    scanf("%d%d%d", &n, &m, &t);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d%d", &a[i], &b[i]);
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = m; j >= a[i]; j--)
        {
            for (int k = t; k >= b[i]; k--)
            {
                dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + 1);
            }
        }
    }
    printf("%d\n", dp[m][t]);
    return 0;
}

洛谷 P1757 通天之分组背包

思路:

正如题目名所说,这是一道分组背包的题

只要在01背包板子上改动一点:求背包的组数(直接在输入时求就行了),然后加层循环就行了

#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int cnt[N];
int dp[N];
int a[N], b[N];
int vis[N][N];
int main()
{
    int m, n;
    scanf("%d%d", &m, &n);
    int ans;
    int maxn = 0;
    for (int i = 1; i <= n; i++)
    {
        scanf("%d%d%d", &a[i], &b[i], &ans);
        ++cnt[ans];
        vis[ans][cnt[ans]] = i;
        maxn = max(maxn, ans);
    }
    for (int i = 1; i <= maxn; i++)
    {
        for (int j = m; j >= 0; j--)
        {
            for (int k = 1; k <= cnt[i]; k++)
            {
                if (j >= a[vis[i][k]])
                {
                    dp[j] = max(dp[j], dp[j - a[vis[i][k]]] + b[vis[i][k]]);
                }
            }
        }
    }
    printf("%d\n", dp[m]);
    return 0;
}

洛谷 P1064 [NOIP2006 提高组] 金明的预算方案

思路:带有附件的背包问题,它属于01背包的变式。这题还好,每一个物品最多只有两个附件,那么我们在对主件进行背包的时候,决策就不再是两个了,而是五个。

分别是:

1.不选,然后去考虑下一个

2.选且只选这个主件

3.选这个主件,并且选附件1

4.选这个主件,并且选附件2

5.选这个主件,并且选附件1和附件2.

我们令dpmainv数组表示某个主件的费用,而dpmainw数组表示某个主件的价值。

同样的,用二维数组dpannexv表示某个附件的费用,dpannexw表示某个附件的价值,第二维只需要0,1,2这三个数,其中第二维是0的场合表示这个主件i的附件数量,它只能等于0或1或2。第二维是1或者是2的值代表以i为主件的附件1或者附件2的相关信息(费用 价值)。

这样,状态转移方程就是四个。

不选附件的①:dp[j] = max(dp[j], dp[j - dpmainv[i]] + dpmainw[i]);

选附件1的②:dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1]] + dpmainw[i] + dpannexw[i][1]);

选附件2的③:dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][2]);

选附件1和附件2的④:dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][1] + dpannexw[i][2]);

#include <bits/stdc++.h>
using namespace std;
const int N = 4e4 + 10;
int dpmainv[N], dpmainw[N];
int dpannexv[N][3], dpannexw[N][3];
int cnt[N];
int dp[N];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    int a, b, q;
    for (int i = 1; i <= m; i++)
    {
        scanf("%d%d%d", &a, &b, &q);
        if (q == 0)
        {
            dpmainv[i] = a;
            dpmainw[i] = a * b;
        }
        else
        {
            cnt[q]++;
            dpannexv[q][cnt[q]] = a;
            dpannexw[q][cnt[q]] = a * b;
        }
    }
    for (int i = 1; i <= m; i++)
    {
        for (int j = n; j >= dpmainv[i] && dpmainv[i] > 0; j--)
        {
            dp[j] = max(dp[j], dp[j - dpmainv[i]] + dpmainw[i]);
            if (j >= dpmainv[i] + dpannexv[i][1])
            {
                dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1]] + dpmainw[i] + dpannexw[i][1]);
            }
            if (j >= dpmainv[i] + dpannexv[i][2])
            {
                dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][2]);
            }
            if (j >= dpmainv[i] + dpannexv[i][1] + dpannexv[i][2])
            {
                dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][1] + dpannexw[i][2]);
            }
        }
    }
    printf("%d\n", dp[n]);
    return 0;
}

洛谷 P1352 没有上司的舞会

思路:

经典的树形dp

dp[ans][0]表示以ans为根的子树,且ans不参加舞会的最大快乐值

dp[ans][1]表示以ans为根的子树,且ans参加了舞会的最大快乐值

则dp[ans][0]+=max(dp[cnt][0], dp[cnt][1]);(cnt是ans的儿子)

dp[ans][1] += dp[cnt][0]; (cnt是ans的儿子)

这题首先找到根几点,然后dfs向下遍历,dp转移就行了

#include <bits/stdc++.h>
using namespace std;
const int N = 6e3 + 10;
int a[N];
int dp[N][2];
int vis[N];
vector<int> v[N];
void dfs(int ans)
{
    dp[ans][1] = a[ans];
    for (int i = 0; i < v[ans].size(); i++)
    {
        int cnt = v[ans][i];
        dfs(cnt);                                  //往下遍历
        dp[ans][0] += max(dp[cnt][0], dp[cnt][1]); //他的下属参加还是不参加
        dp[ans][1] += dp[cnt][0];                  //此时下属不能参加
    }
}
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }
    int p, q;
    for (int i = 1; i <= n - 1; i++)
    {
        scanf("%d%d", &p, &q);
        v[q].push_back(p);
        vis[p] = 1; //把职员标记上
    }
    int ans = 0;
    for (int i = 1; i <= n; i++)
    {
        if (vis[i] == 0) //找最高职位,也就是树的根
        {
            ans = i;
            break;
        }
    }
    dfs(ans);
    printf("%d\n", max(dp[ans][0], dp[ans][1]));
    return 0;
}

能量项链

思路:一道典型的区间dp题,对于一般的n颗珠子,我们求它的最大能量值。我们可以把它的上一步的时候的组成分为dp[1]max,dp[n-1]max; dp[2]max,dp[n-2]max; dp[3]max,dp[n-3]max ……dp[n]max,dp[1]max;它右这n种情况的合并而成我们只要求出这n种情况的最大值即可求出n颗珠子时的能量的最大值。但是别忘了,题目是没有说起点在哪里的,也没有说方向是向哪里的,因此我们还得以每一个点为起点做一次分组算一遍。

状态转移方程:dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]);

#include <iostream>
#include <cstdio>
#include <cstring>
#include <math.h>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2e2 + 10;
int a[N];
int dp[N][N];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        a[i + n] = a[i];
    }
    // 2 3 5 10 2 3 5 10
    //还是环形石子问题
    for (int len = 3; len <= n + 1; len++) //最少要三个值才能聚合两个珠子,但最后一个值最后还会用到
    {
        for (int l = 1; l + len - 1 <= 2 * n; l++)
        {
            int r = l + len - 1;
            for (int k = l + 1; k < r; k++) //由于每颗珠子分首尾,所以k最小也得是l+1
            {
                dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]); //由于k位置这个元素会重复用到,所以不用加一!!!
            }
        }
    }
    int maxn = 0;
    for (int i = 1; i <= n; i++)
    {
        maxn = max(maxn, dp[i][n + i]);
        // printf("%d\n", dp[i][i + n]);
    }
    printf("%d\n", maxn);
    return 0;
}

HDU - 2639 

思路:01背包+第k最优解

#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int dp[N][N];
int a[108], b[108];
int c[108], d[108];
int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        memset(dp, 0, sizeof(dp));
        memset(c, 0, sizeof(c));
        memset(d, 0, sizeof(d));
        int n, v, p;
        scanf("%d%d%d", &n, &v, &p);
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &a[i]);
        }
        for (int i = 1; i <= n; i++)
        {
            scanf("%d", &b[i]);
        }
        for (int i = 1; i <= n; i++)
        {
            for (int j = v; j >= b[i]; j--)
            {
                for (int k = 1; k <= p; k++)
                {
                    c[k] = dp[j][k]; //体积为j时的第k大价值
                    d[k] = dp[j - b[i]][k] + a[i];
                }
                int q = 1, l = 1, r = 1;
                while (q <= p && (l <= p || r <= p))
                {
                    if (c[l] >= d[r]) //比较两个数的大小,谁大就用谁赋值
                    {
                        dp[j][q] = c[l++];
                    }
                    else
                    {
                        dp[j][q] = d[r++];
                    }
                    if (dp[j][q] != dp[j][q - 1]) //这里要去一下重
                    {
                        q++;
                    }
                }
            }
        }
        printf("%d\n", dp[v][p]);
    }
    return 0;
}

HDU - 5323

思路:根据线段树 父亲节点的区间 和 孩子节点的 区间的关系,可得知 若孩子节点是[l,r],则父亲节点有4种情况即[l - 1 - (r - l), r],[l, r + 1 + r - l],[l - 1 - (r - l) - 1, r],[l, r + 1 + r - l - 1]

然后暴力搜索即可,注意当r >= ans和l - 1 - (r - l) < 0时就要return;

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans;
void dfs(ll l, ll r) //建立左区间和右区间递归搜索
{
    if (l == 0)
    {
        if (r != 0)
        {
            ans = min(ans, r);
        }
    }
    if (r >= ans) //右边界
    {
        return;
    }
    if (l - 1 - (r - l) < 0) //左边界
    {
        return;
    }
    dfs(l - 1 - (r - l), r); //奇数区间
    dfs(l, r + 1 + r - l);
    dfs(l - 1 - (r - l) - 1, r); //偶数区间
    dfs(l, r + 1 + r - l - 1);
}
int main()
{
    ll l, r;
    while (scanf("%lld%lld", &l, &r) != EOF)
    {
        ans = 0x3f3f3f3f;
        dfs(l, r);
        if (l == 0 && r == 0)
        {
            printf("0\n");
        }
        else
        {
            if (ans == 0x3f3f3f3f)
            {
                printf("-1\n");
            }
            else
            {
                printf("%lld\n", ans);
            }
        }
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值