ACM DP入门题(附代码解释)

目录

HDU 1171 Big Event in HDU(多重背包)

HDU 1224 Free DIY Tour

HDU 1421 搬寝室

HDU1069 Monkey and Banana(DAG模型)

HDU1506 Largest Rectangle in a Histogram

HDU 1257 最少拦截系统 (最长上升子序列)

HDU 1505 City Game

HDU 1864 最大报销额

HDU 1398 Square Coins

POJ 2392 Space Elevator (多重背包问题)

HDU 1631 Bridging signals

HDU 2844 Coins

HDU 1159 Common Subsequence(最长公共子序列)

POJ 3616 Milking Time

POJ 2385 Apple Catching

POJ1088 滑雪

POJ 1384 Piggy-Bank

HDU 2084 数塔(经典DP题)

HDU 1003 Max Sum O(n)复杂度

HDU 2159 FATE(完全背包)

POJ 2184 Cow Exhibition

HDU 1176 免费馅饼

HDU 1789 Doing Homework again

HDU 2191悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(多重背包)

HDU 1203 I NEED A OFFER!(01背包) 

CodeForces - 544C Writing Code(完全背包) 

HDU 3496 Watch The Movie 

POJ 3181 Dollar Dayz (完全背包+大数) 

CodeForces - 106C Buns(多重背包) 


HDU 1171 Big Event in HDU(多重背包)

题意:给出每个物体的价值和物体的数量,如何分使得A,B所得价值最接近并且A的价值不能小于B。

题解:将总和分成一半,然后求最接近值就行了。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

using namespace std;

int main() {
    int n, w[100], s[100], dp[125001];
    while (scanf("%d", &n) && n >= 0) {
        int sum = 0;
        for (int i = 0; i < n; i++) {
            cin >> w[i] >> s[i];
            sum += w[i] * s[i];
        }
        int sam = sum >> 1;
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i < n; i++)
            for (int t = 0; t < s[i]; t++)
                for (int j = sam; j >= w[i]; j--)
                    dp[j] = max(dp[j], dp[j - w[i]] + w[i]);
        cout << sum - dp[sam] << " " << dp[sam] << endl;
    }
    return 0;
}

 

HDU 1224 Free DIY Tour

题意:给你一些点(n个),然后每个点都有一个权值,每走到一个点就可以得到这个点这么多的价值,然后给出边,问你从起点(1号点)走到第n+1号点,怎样走才可以使得到的价值最大,同时打印路径。同时还规定了这个图有某些特殊条件,1、只存在从编号小的点去到编号大的点,2、起点的价值为0。

题解:首先用一个二维数组记录哪些点是连通的,声明一个数组记录每个点之前的最大的一个点,注意最后输出的时候需要用递归输出,加上注意输出格式就行了。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define me(a) memset(a,0,sizeof(a))
using namespace std;
int pre[1005];

void print(int i) {
    if (i == 1) {
        cout << "1";
        return;
    }
    print(pre[i]);
    cout << "->" << i;
}

int main() {
    int t, a[105], dp[1005], vis[105][105], x, y, n, m;
    cin >> t;
    for (int tt = 1; tt <= t; tt++) {
        cin >> n;
        me(a);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        cin >> m;
        me(vis), me(dp);
        for (int i = 1; i <= m; i++) {
            scanf("%d%d", &x, &y);
            vis[x][y] = 1;
        }
        a[n + 1] = 0;
        for (int i = 1; i <= n + 1; i++)
            for (int j = 1; j < i; j++)
                if (vis[j][i])
                    if (dp[i] < a[i] + dp[j]) {
                        dp[i] = a[i] + dp[j];
                        pre[i] = j;
                    }
        cout << "CASE " << tt << "#" << endl;
        cout << "points : " << dp[n + 1] << endl;
        cout << "circuit : ";
        print(pre[n + 1]);
        cout << "->1" << endl;
        if (tt < t)
            cout << endl;
    }
    return 0;
}

 

HDU 1421 搬寝室

题解:这个应该也算一道背包问题吧,他的疲劳度是相邻两个物品的重量差的平方,首先是排好序,再重里面找k个最小的相邻差,再取相邻差的时候,声明一个二维的dp数组,用来表示取了多少个相邻差,疲劳度是多少。注意,每个两个相邻的相邻差中只能取一个,这个应该容易想通。状态转移方程  dp[i][j]=min(dp[i][j-1],dp[i-1][j-2]+(a[j]-a[j-1])*(a[j]-a[j-1]));

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 2e3 + 5;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
int dp[maxn][maxn];

int main() {
    int n, k;
    while (cin >> n >> k) {
        int a[maxn];
        me(dp, 0);
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        sort(a + 1, a + n + 1);
        for (int i = 1; i <= k; i++)
            for (int j = 1; j <= n; j++)
                if (2 * i <= j)
                    dp[i][j] = min(dp[i][j - 1], dp[i - 1][j - 2] + (a[j] - a[j - 1]) * (a[j] - a[j - 1]));
                else
                    dp[i][j] = inf;
        cout << dp[k][n] << endl;
    }
    return 0;
}

 

HDU1069 Monkey and Banana(DAG模型)

题解:一道很不错的dp题,最后可以化为求和最大的和的子序列。因为长方体分六个面,所以每个长宽高都可以做其他两项,等于每个数都可以做长宽高,所以在输入的时候就进行处理,然后按长从小到大排序,如果长相等,宽就从小到大排序,最后这样找高的和最大的和的子序列。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
struct node {
    int l, k, g;
};

int cmp(node a, node b) {
    if (a.l == b.l)
        return a.k < b.k;
    return a.l < b.l;
}

int main() {
    int n, ss = 1;
    while (cin >> n && n) {
        node a[200];
        int l, k, g;
        int len = 0;
        for (int i = 0; i < n; i++) {
            cin >> l >> k >> g;
            a[len].l = l, a[len].k = k, a[len++].g = g;
            a[len].l = l, a[len].k = g, a[len++].g = k;
            a[len].l = k, a[len].k = l, a[len++].g = g;
            a[len].l = k, a[len].k = g, a[len++].g = l;
            a[len].l = g, a[len].k = l, a[len++].g = k;
            a[len].l = g, a[len].k = k, a[len++].g = l;
        }
        sort(a, a + len, cmp);
        int dp[200];
        memset(dp, 0, sizeof(dp));
        int sum = 0;
        for (int i = 0; i < len; i++) {
            int ma = 0;
            for (int j = 0; j < i; j++)
                if (a[i].l > a[j].l && a[i].k > a[j].k)
                    ma = max(ma, dp[j]);
            dp[i] = ma + a[i].g;
            if (sum < dp[i])
                sum = dp[i];
        }
        cout << "Case " << ss++ << ": " << "maximum height = ";
        cout << sum << endl;
    }
    return 0;
}

 

HDU1506 Largest Rectangle in a Histogram

题解:由题得,柱形的高度,就是对应柱形的面积,所以要求的是最低高度最小的一段柱形。可以声明两个数组记录所遍历柱形的左边和右边比该柱形,比该矩形高度高的的柱形的位置。最后在遍历一次,选出最大的面积。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
int a[300005];
int l[300005], r[300005];

int main() {
    int n;
    while (scanf("%d", &n) && n) {
        memset(a, 0, sizeof(a));
        memset(l, 0, sizeof(l));
        memset(r, 0, sizeof(r));
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        for (int i = 0; i < n; i++) {
            l[i] = i;
            while (l[i] > 0 && a[l[i] - 1] >= a[i])
                l[i] = l[l[i] - 1];
        }
        for (int i = n - 1; i >= 0; i--) {
            r[i] = i;
            while (r[i] < n - 1 && a[r[i] + 1] >= a[i])
                r[i] = r[r[i] + 1];
        }
        long long maxn = -1;
        for (int i = 0; i < n; i++)
            if ((long long) (r[i] - l[i] + 1) * a[i] > maxn)
                maxn = (long long) (r[i] - l[i] + 1) * a[i];
        cout << maxn << endl;
    }
    return 0;
}

 

HDU 1257 最少拦截系统 (最长上升子序列)

题解:就是求最长上升子序列,这里是O(nlogn)的做法(O(n^2)这个题也能过)(不懂nlogn的做法可以看看这篇博客传送门

#pragma comment(linker, "/STACK:102400000,102400000")

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int mod = 998244353;
const int maxn = 3e4 + 5;
const int inf = 1e9;
const long long onf = 1e18;
#define me(a, b) memset(a,b,sizeof(a))
#define lowbit(x) x&(-x)
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define PI 3.14159265358979323846
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
int a[maxn], dp[maxn];

int main() {
    int n;
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        int len = 0;
        for (int i = 1; i <= n; i++) {
            int pos = lower_bound(dp, dp + len, a[i]) - dp;
            if (pos == len)
                dp[len++] = a[i];
            else
                dp[pos] = a[i];
        }
        printf("%d\n", len);
    }
    return 0;
}

 

HDU 1505 City Game

 题意:给出一个矩阵,上面是可以用的土地和不能用的土地,要在上面选一块矩形的土地建房子,问最大能选多大的土地,土地每单位3块大洋,最后输出租金。

这是上面HDU 1506的加强版,思想都是差不多的。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>

#define m(a) memset(a,0,sizeof(a))
using namespace std;
char maps[1005][1005];
int l[1005], r[1005], dp[1005][1005];

int main() {
    int t;
    cin >> t;
    string s;
    while (t--) {
        int n, m;
        cin >> n >> m;
        m(dp);
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) {
                cin >> s;
                if (s == "F")
                    dp[i][j] = dp[i - 1][j] + 1;
                else
                    dp[i][j] = 0;
            }
        m(l), m(r);
        int maxn = 0;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++)
                if (dp[i][j]) {
                    l[j] = j;
                    while (l[j] > 1 && dp[i][j] <= dp[i][l[j] - 1])
                        l[j] = l[l[j] - 1];
                }
            for (int j = m; j > 0; j--)
                if (dp[i][j]) {
                    r[j] = j;
                    while (r[j] < m && dp[i][j] <= dp[i][r[j] + 1])
                        r[j] = r[r[j] + 1];
                }
            for (int j = 1; j <= m; j++) {
                int s = (r[j] - l[j] + 1) * dp[i][j];
                if (s > maxn)
                    maxn = s;
            }
        }
        cout << maxn * 3 << endl;
    }
    return 0;
}

 

HDU 1864 最大报销额

题解:一个01背包问题,但是金额是有小数的。所以本题的解题思路是将题中所给金额全部乘100,然后在最后输出的时候再除以100,值得注意的是,题中说的其中的一类的金额不超过600,而不是单个物品的金额超过600,在01背包遍历之前就判断哪些发票可以报销,不能报销的发票标记一下。

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<string>

using namespace std;

struct node {
    double sum;
    int flog;

    node() {
        sum = 0, flog = 1;
    }
};

double dp[3000005];

int main() {
    double q, m;
    int n, s;
    char c;
    while (scanf("%lf%d", &q, &n) && n) {
        node a[35];
        q *= 100;
        for (int i = 0; i < n; i++) {
            cin >> s;
            double va = 0, vb = 0, vc = 0;
            for (int j = 0; j < s; j++) {
                scanf("%*c%c:%lf", &c, &m);
                if (c == 'A')
                    va += m;
                else if (c == 'B')
                    vb += m;
                else if (c == 'C')
                    vc += m;
                else {
                    a[i].flog = 0;
                    break;
                }
                if (va > 600 || vb > 600 || vc > 600 || va + vb + vc > 1000)
                    a[i].flog = 0;
                else
                    a[i].sum = (va + vb + vc) * 100;
            }
        }
        memset(dp, 0.0, sizeof(dp));
        for (int i = 0; i < n; i++) {
            if (a[i].flog)
                for (int j = (int) q; j >= (int) a[i].sum; j--)
                    dp[j] = max(dp[j], dp[j - (int) a[i].sum] + a[i].sum);
        }
        printf("%.2lf\n", dp[(int) q] / 100.0);
    }
    return 0;
}

 

HDU 1398 Square Coins

题解:每个数都可以由小于等于他的平方数加若干个1得到,所以一个数的组合就是小于它的所有平方数的组合个数的和。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 3e2 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;
int a[maxn];

int main() {
    int n;
    int a[35];
    for (int i = 1; i * i <= 289; i++)
        a[i] = i * i;
    int dp[305];
    me(dp);
    dp[0] = 1;
    for (int i = 1; i <= 17; i++)
        for (int j = 1; j <= 300; j++)
            if (j - a[i] >= 0)
                dp[j] += dp[j - a[i]];
    while (cin >> n && n)
        cout << dp[n] << endl;
    return 0;
}

 

POJ 2392 Space Elevator (多重背包问题)

题解:将所有的块数按最大的限制长度重小到大排序后,就会发现这是一个多重背包问题,但是也可以用完全背包做,而且完全背包的时间复杂度还小一些,下面给出两种做法的代码。

多重背包:

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 5e2 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;

struct node {
    int h, m, c;

    bool friend operator<(node a, node b) {
        return a.m < b.m;
    }
};

int main() {
    node a[maxn];
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        scanf("%d%d%d", &a[i].h, &a[i].m, &a[i].c);
    sort(a, a + n);
    int dp[40005], num[40005];
    me(dp), me(num);
    int ma = 0;
    for (int i = 0; i < n; i++) {
        for (int t = 0; t < a[i].c; t++)
            for (int j = a[i].m; j >= a[i].h; j--) {
                dp[j] = max(dp[j - a[i].h] + a[i].h, dp[j]);
                if (dp[j] > ma)
                    ma = dp[j];
            }
    }
    cout << ma << endl;
    return 0;
}

完全背包:

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 5e2 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;

struct node {
    int h, m, c;

    bool friend operator<(node a, node b) {
        return a.m < b.m;
    }
};

int main() {
    node a[maxn];
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        scanf("%d%d%d", &a[i].h, &a[i].m, &a[i].c);
    sort(a, a + n);
    int dp[40005], num[40005];
    me(dp);
    int maxn = 0;
    dp[0] = 1;
    for (int i = 0; i < n; i++) {
        me(num);
        for (int j = a[i].h; j <= a[i].m; j++) {
            if (dp[j - a[i].h] && !dp[j] && num[j - a[i].h] + 1 <= a[i].c) {
                dp[j] = 1;
                num[j] = num[j - a[i].h] + 1;
                if (j > maxn)
                    maxn = j;
            }
        }
    }
    cout << maxn << endl;
    return 0;
}

 

HDU 1631 Bridging signals

题解:最长不下降子序列,这个题n^2的做法就要超时,只能是nlogn的做法。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 4e4 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;

int main() {
    int a[maxn];
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        int len = 0, dp[maxn];
        for (int i = 1; i <= n; i++) {
            int pos = upper_bound(dp, dp + len, a[i]) - dp;
            if (pos == len)
                dp[len++] = a[i];
            else
                dp[pos] = a[i];
        }
        cout << len << endl;
    }
    return 0;
}

 

HDU 2844 Coins

题解:多重背包问题,但是不是简单的多重。两种做法。一,多个背包客一起嵌套使用但是效率不高,代码如下。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define INF 0x3f3f3f3f
using namespace std;
int m, dp[100005];

void ny(int v) {
    for (int i = m; i >= v; i--)
        dp[i] = max(dp[i], dp[i - v] + v);
}

void wq(int v) {
    for (int i = v; i <= m; i++)
        dp[i] = max(dp[i], dp[i - v] + v);
}

void dc(int a, int b) {
    if (a * b >= m)
        wq(a);
    else {
        for (int j = 1; j <= b; j <<= 1) {
            ny(j * a);
            b -= j;
        }
        ny(b * a);
    }
}

int main() {
    int n, a[2005], b[2005];
    while (~scanf("%d%d", &n, &m)) {
        if (n + m == 0)
            break;
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        for (int i = 0; i < n; i++)
            scanf("%d", &b[i]);
        fill(dp, dp + m + 1, -INF);
        dp[0] = 0;
        for (int i = 0; i < n; i++)
            dc(a[i], b[i]);
        int sum = 0;
        for (int i = 1; i <= m; i++)
            if (dp[i] > 0)
                sum++;
        cout << sum << endl;
    }
    return 0;
}

另一种,将多重背包化为完全背包,每次记录不一样的值得数目,并且取数的数目不大于题中所给的数目,代码如下。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>

using namespace std;
const int maxn = 1e5 + 5;
int m, dp[maxn], s[maxn];

int main() {
    int n, a[205], b[205];
    while (scanf("%d%d", &n, &m) != EOF) {
        if (n + m == 0)
            break;
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        for (int i = 0; i < n; i++)
            scanf("%d", &b[i]);
        fill(dp, dp + maxn, 0);
        dp[0] = 1;
        int sum = 0;
        for (int i = 0; i < n; i++) {
            memset(s, 0, sizeof(s));
            for (int j = a[i]; j <= m; j++)
                if (dp[j - a[i]] && !dp[j] && s[j - a[i]] < b[i]) {
                    dp[j] = 1;
                    s[j] = s[j - a[i]] + 1;
                    sum++;
                }
        }
        cout << sum << endl;
    }
    return 0;
}

 

HDU 1159 Common Subsequence(最长公共子序列)

题解:模板最长公共子序列。

#include<iostream>
#include<string>
#include<cstring>
#include<algorithm>

using namespace std;
int dp[1005][1005];

int main() {
    char s[1000], x[1000];
    while (cin >> (s + 1) >> (x + 1)) {
        int l1 = strlen(s + 1), l2 = strlen(x + 1);
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= l1; i++)
            for (int j = 1; j <= l2; j++) {
                if (s[i] == x[j])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                else
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        cout << dp[l1][l2] << endl;
    }
    return 0;
}

 

POJ 3616 Milking Time

题解:一个最长子序列和得问题,由题得,可将开始产奶时间由小到大排序,然后再找产奶量最多得产奶变量的和最大的子序列和。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 1e3 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;

struct node {
    int l, r, w;

    bool friend operator<(node a, node b) {
        return a.l < b.l;
    }
};

int main() {
    node a[maxn];
    int t, m, r;
    scanf("%d%d%d", &t, &m, &r);
    for (int i = 0; i < m; i++) {
        scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].w);
        a[i].r += r;
    }
    sort(a, a + m);
    int dp[maxn];
    me(dp);
    int maxn = 0;
    for (int i = 0; i < m; i++) {
        int ma = 0;
        for (int j = 0; j < i; j++)
            if (a[i].l >= a[j].r)
                ma = max(ma, dp[j]);
        dp[i] = ma + a[i].w;
        maxn = max(maxn, dp[i]);
    }
    cout << maxn << endl;
    return 0;
}

 

POJ 2385 Apple Catching

题解:声明一个二维数组,表示在某时刻走了几次能某吃到的最大苹果。在当前位置时,有两种情况,一是上一时刻也在这棵树下。二,上一时刻没有在当前树下。所以状态转移方程为  dp[i][j]=max(dp[i-1][j],dp[i-1][j-1]);注:当w等于0的时刻,本时刻吃的苹果树只能是上一时刻在当前时刻树下吃的苹果数。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 1e3 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;

int main() {
    int t, w, a[maxn];
    cin >> t >> w;
    for (int i = 1; i <= t; i++)
        scanf("%d", &a[i]);
    int dp[maxn][35];
    me(dp);
    int maxn = 0;
    for (int i = 1; i <= t; i++)
        for (int j = 0; j <= w; j++) {
            if (j == 0)
                dp[i][j] = dp[i - 1][j];
            else
                dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - 1]);///共两种状态,上一时刻同一棵树或上一时刻不同的树;
            if (a[i] - 1 == j % 2)
                dp[i][j]++;
            if (maxn < dp[i][j])
                maxn = dp[i][j];
        }
    cout << maxn << endl;
    return 0;
}

 

POJ1088 滑雪

题解:记忆化搜索,枚举每点,取最大值。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 1e3 + 5;
const int mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;
int a[maxn][maxn], dp[maxn][maxn];
int fx[4][2] = {0, 1, 0, -1, -1, 0, 1, 0};
int m, n;

int check(int x, int y) {
    if (x < 1 || y < 1 || x > m || y > n)
        return 0;
    return 1;
}

int dfs(int x, int y) {
    if (!dp[x][y]) {
        for (int i = 0; i < 4; i++) {
            int x1 = x + fx[i][0];
            int y1 = y + fx[i][1];
            if (check(x1, y1) && a[x1][y1] < a[x][y])
                dp[x][y] = max(dp[x][y], dfs(x1, y1));
        }
        dp[x][y]++;
    }
    return dp[x][y];
}

int main() {
    scanf("%d%d", &m, &n);
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            scanf("%d", &a[i][j]);
    me(dp);
    int max = -1;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            if (max < dfs(i, j))
                max = dfs(i, j);
    cout << max << endl;
    return 0;
}

 

POJ 1384 Piggy-Bank

题解:完全背包问题,问固定重量,这个猪里面满足这个重量最小的金额是多少。与一般背包问题不同,这个背包问题是求最小满足重量,所以要开始把dp数组每项赋值为最大,然后转化方程里面每次选择较小值。状态转换方程 dp[j]=min(dp[j],dp[j-w[i]]+p[i]);。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define inf 0x3f3f3f3f
using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int e, f, n;
        int p[505], w[505];
        cin >> e >> f >> n;
        int s = f - e;
        for (int i = 0; i < n; i++)
            scanf("%d%d", &p[i], &w[i]);
        int dp[10005];
        fill(dp, dp + s + 1, inf);
        dp[0] = 0;
        for (int i = 0; i < n; i++)
            for (int j = w[i]; j <= s; j++)
                dp[j] = min(dp[j], dp[j - w[i]] + p[i]);
        if (dp[s] == inf)
            cout << "This is impossible." << endl;
        else
            cout << "The minimum amount of money in the piggy-bank is " << dp[s] << "." << endl;
    }
    return 0;
}

 

HDU 2084 数塔(经典DP题)

题解:从最下层开始找,状态转移方程dp[i][j]=max(dp[i+1][j],dp[i+1][j+1])+a[i][j];

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        int a[1005][1005], dp[1005][1005];
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= i; j++)
                scanf("%d", &a[i][j]);
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= n; i++)
            dp[n][i] = a[n][i];
        for (int i = n - 1; i > 0; i--)
            for (int j = 1; j <= i; j++)
                dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]) + a[i][j];
        printf("%d\n", dp[1][1]);
    }
    return 0;
}

 

HDU 1003 Max Sum O(n)复杂度

题解:一个DP问题,但是有多种做法,其中前缀和做法O(n^2)的复杂度,在这里要超时,下面介绍一种O(n)复杂度的做法。声明一个dp数组,dp数组记录的是从第一个数到该点数的最大子序列和,然后找出dp数组中值最大的,就是最长子序列和,还有初末位置要随着最长子序列的长度而变化。这是用结构体就很方便。O(n)复杂度的状态转移方程式。dp[i].max=max(a[i],dp[i-1].max+a[i]);。
 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
struct node {
    int x, y, max;
};

int main() {
    int t;
    cin >> t;
    for (int s = 1; s <= t; s++) {
        int n;
        cin >> n;
        int a[100005];
        node dp[100005];
        memset(dp, 0, sizeof(dp));
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i]);
        dp[0].max = a[0];
        for (int i = 1; i < n; i++) {
            //dp[i].max=max(a[i],dp[i-1].max+a[i]);
            if (a[i] > dp[i - 1].max + a[i]) {
                dp[i].max = a[i];
                dp[i].x = i;
                dp[i].y = i;
            } else {
                dp[i].max = dp[i - 1].max + a[i];
                dp[i].x = dp[i - 1].x;
                dp[i].y = dp[i - 1].y + 1;
            }
        }
        int ss = 0;
        for (int i = 1; i < n; i++)
            if (dp[ss].max < dp[i].max) {
                ss = i;
            }
        cout << "Case " << s << ":" << endl;
        printf("%d %d %d\n", dp[ss].max, dp[ss].x + 1, dp[ss].y + 1);
        if (s < t)
            cout << endl;
    }
    return 0;
}

 

HDU 2159 FATE(完全背包)

题解:完全背包问题,但是注意他还有一个最多杀怪数,所以要声明一个数组记录杀怪的数目,当dp数组更新时,记录杀怪数组的值要跟着跟新。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e2 + 5;
const int mod = 10000;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;

int main() {
    int n, m, k, s;
    while (~scanf("%d%d%d%d", &n, &m, &k, &s)) {
        int a[maxn], b[maxn];
        for (int i = 0; i < k; i++)
            scanf("%d%d", &a[i], &b[i]);
        int dp[maxn], num[maxn];
        me(dp, 0), me(num, 0);
        for (int i = 0; i < k; i++)
            for (int j = b[i]; j <= m; j++)
                if (dp[j] < dp[j - b[i]] + a[i] && num[j - b[i]] + 1 <= s)
                    dp[j] = dp[j - b[i]] + a[i], num[j] = num[j - b[i]] + 1;
        int flog = 0, fin = 0;
        for (int i = 0; i <= m; i++)
            if (dp[i] >= n) {
                flog = 1, fin = i;
                break;
            }
        if (flog)
            cout << m - fin << endl;
        else
            cout << "-1" << endl;
    }
    return 0;
}

 

POJ 2184 Cow Exhibition

题解:01背包问题,但是数值里面有负值。当遇到负值时,需要反向循环。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>

const int maxn = 2e5 + 5;
using namespace std;

int main() {
    int n, a[105], b[105];
    cin >> n;
    for (int i = 0; i < n; i++)
        scanf("%d%d", &a[i], &b[i]);
    int dp[maxn];
    fill(dp, dp + maxn, -100005);
    dp[100000] = 0;
    for (int j = 0; j < n; j++) {
        if (a[j] >= 0)
            for (int i = maxn - 1; i >= a[j]; i--)
                dp[i] = max(dp[i], dp[i - a[j]] + b[j]);
        else
            for (int i = 0; i <= maxn - 1 + a[j]; i++)
                dp[i] = max(dp[i], dp[i - a[j]] + b[j]);
    }
    int ma = 0;
    for (int i = 100000; i < maxn; i++) {
        if (dp[i] >= 0)
            ma = max(dp[i] + i - 100000, ma);
    }
    cout << ma << endl;
    return 0;
}

 

HDU 1176 免费馅饼

题解:本题是一个类似于数塔的dp题。在本题中,可以用一个二维数组保存在某时刻某地点有馅饼掉下来,然后再声明一个dp数组,dp数组表示在该点能够接到的最大馅饼数。状态转移方程为:dp[i][j]=max(dp[i-1][j+1],max(dp[i+1][j+1],dp[i][j+1]))+a[i][j];。意思是,在当前点,你可以去接你前面或后面点的馅饼,也可以留在原地接下一时刻掉下来的馅饼。
 

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;
int a[15][100005], dp[15][100005];

int main() {
    int n;
    while (scanf("%d", &n) && n) {
        int x, t;
        memset(a, 0, sizeof(a));
        int tmax = -1;
        for (int i = 0; i < n; i++) {
            scanf("%d%d", &x, &t);
            a[++x][t]++;
            tmax = max(tmax, t);
        }
        memset(dp, 0, sizeof(dp));
        for (int j = tmax; j >= 0; j--)
            for (int i = 1; i <= 11; i++)
                dp[i][j] = max(dp[i - 1][j + 1], max(dp[i + 1][j + 1], dp[i][j + 1])) + a[i][j];
        cout << dp[6][0] << endl;
    }
    return 0;
}

 

HDU 1789 Doing Homework again

题解:这个可以归类为一个贪心问题,首先按扣分多的排序,如果扣分一样,就按天数少的排序,接着按天数从后向前遍历,只要前面有一天是空着的,就可以拿来做当前的作业,如果没有,则加上该科要扣的分数,但是有一点要注意,就是每次遍历的时候从后向前,这样前面的时间可以留个截止日期短的科目。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;

struct node {
    int x, t;

    bool friend operator<(node a, node b) {
        if (a.x == b.x)
            return a.t < b.t;
        return a.x > b.x;
    }
} a[maxn];

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i].t);
        for (int i = 0; i < n; i++)
            scanf("%d", &a[i].x);
        sort(a, a + n);
        int vis[maxn];
        me(vis, 0);
        int sum = 0;
        for (int i = 0; i < n; i++) {
            int flog = 1;
            for (int j = a[i].t; j > 0; j--)
                if (!vis[j]) {
                    vis[j] = 1, flog = 0;
                    break;
                }
            if (flog)
                sum += a[i].x;
        }
        cout << sum << endl;
    }
    return 0;
}

 

HDU 2191悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(多重背包)

题解:裸的多重背包。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 2e2 + 5;
const int mod = 10000;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
struct node {
    int w, v, s;
} a[maxn];

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n, m;
        cin >> n >> m;
        for (int i = 0; i < m; i++)
            scanf("%d%d%d", &a[i].w, &a[i].v, &a[i].s);
        int dp[maxn];
        me(dp, 0);
        for (int i = 0; i < m; i++)
            for (int t = 0; t < a[i].s; t++)
                for (int j = n; j >= a[i].w; j--)
                    dp[j] = max(dp[j], dp[j - a[i].w] + a[i].v);
        cout << dp[n] << endl;
    }
    return 0;
}

 

HDU 1203 I NEED A OFFER!(01背包) 

题解:本题是一个裸的01背包问题,但是需要转化,题目是求至少收到一份录取书的概率,则应该反过来求一份录取书都收不到的最小概率,再用这个概率去减去一分录取书都收不到的概率才是最少收到一份录取书的概率。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
#include<queue>
#include<set>
#include<cmath>
#include<stack>
#include<string>

const int maxn = 1e4 + 5;
const int mod = 1e9 + 7;
const int inf = 1e9;
#define me(a, b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;

int main() {
    int n, m, a[maxn];
    double dp[maxn], b[maxn];
    while (cin >> m >> n && m + n) {
        fill(dp, dp + maxn, 1);
        for (int i = 0; i < n; i++)
            cin >> a[i] >> b[i];
        for (int i = 0; i < n; i++)
            for (int j = m; j >= a[i]; j--)
                dp[j] = min(dp[j - a[i]] * (1 - b[i]), dp[j]);
        printf("%.1f%%\n", (1 - dp[m]) * 100);
    }
    return 0;
}

 

CodeForces - 544C Writing Code(完全背包) 

题解:声明一个二维数组,记录多少程序员参与写代码,有多少bug数量,最多的有多少种情况。且当前人数写了代码的bug数量,是前面人数写的bug数加上当前人数写的bug的数量。于是又状态转移方程 dp[j][t]+=dp[j-1][t-a[i]];又0个人写代码有有0个bug情况只有一种,所以dp[0][0]=1;
 

#include<iostream>
#include<cstring>

#define me(a) memset(a,0,sizeof(a))
using namespace std;

int main() {
    int m, n, b, mod, a[505];
    cin >> n >> m >> b >> mod;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    int dp[505][505];
    me(dp);
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            for (int t = a[i]; t <= b; t++) {
                dp[j][t] += dp[j - 1][t - a[i]];
                dp[j][t] %= mod;
            }
    int sum = 0;
    for (int i = 0; i <= b; i++)
        sum = (sum + dp[m][i]) % mod;
    cout << sum << endl;
    return 0;
}

 

HDU 3496 Watch The Movie 

题解:这是一类的题,可以当一个模板,01背包思想,加一个控制选电影个数的循环。

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 1e2 + 5;
const long long mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;
int dp[105][1005];

int main() {
    int t;
    cin >> t;
    while (t--) {
        int m, n, l, a[105], b[105];
        scanf("%d%d%d", &n, &m, &l);
        for (int i = 0; i < n; i++)
            scanf("%d%d", &a[i], &b[i]);
        me(dp);
        for (int i = 0; i < n; i++)
            for (int j = m; j > 0; j--)
                for (int t = l; t >= a[i]; t--)
                    if (dp[j - 1][t - a[i]] || j == 1)
                        dp[j][t] = max(dp[j][t], dp[j - 1][t - a[i]] + b[i]);
        cout << dp[m][l] << endl;
    }
    return 0;
}

 

POJ 3181 Dollar Dayz (完全背包+大数) 

题解:首先题中提到了1到K的钱可以任意使用,可以得出这是一个完全背包,还有最后这个数已经达到了32位,所以用两个long long 数组来存。状态转移方程,可声明一个二维数组dp[i][j],表示用了i种额度的钱,在数目为j的钱的最多组合数目。由题可得,能组成当前钱的组合,一定是前一个钱数的组合加上一个额度的钱得到的。所以得到状态转移方程dp[j]+=dp[j-i];。
 

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 1e3 + 5;
const long long mod = 1e16;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;
long long dp[maxn], dp1[maxn];

int main() {
    int n, k;
    cin >> n >> k;
    me(dp), me(dp1);
    dp[0] = 1;
    for (int i = 1; i <= k; i++)
        for (int j = 1; j <= n; j++)
            if (j >= i) {
                dp[j] += dp[j - i];
                dp1[j] += dp1[j - i];
                dp1[j] += dp[j] / mod;
                dp[j] %= mod;
            }
    if (dp1[n])
        cout << dp1[n];
    cout << dp[n] << endl;
    return 0;
}

 

CodeForces - 106C Buns(多重背包) 

题意:老板有n克面,m种馅,每种馅有ai克,做该种馅的饼要bi克,要用ci克面,做出来的饼可以卖di那么多钱。也可以花c0克面,不要馅,卖d0那么多钱。

题解:这样看来就是一个多重背包问题,但是初始化dp数组的时候需要注意,因为dp【i】数组保存的是用i克面做出来的饼能卖最多的钱,所以初始化的时候直接初始化将i那么多钱做不要馅的饼获得的钱,后面在直接多重背包就行了。
 

#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<string>
#include<map>

const int maxn = 1000000;
const long long mod = 1e9;
#define me(a) memset(a,0,sizeof(a))
#define ll long long
using namespace std;

int main() {
    int a[15], b[15], c[15], d[15], n, m, c0, d0;
    scanf("%d%d%d%d", &n, &m, &c0, &d0);
    for (int i = 0; i < m; i++)
        scanf("%d%d%d%d", &a[i], &b[i], &c[i], &d[i]);
    int dp[10005];
    me(dp);
    for (int i = c0; i <= n; i++)
        dp[i] = i / c0 * d0;
    for (int i = 0; i < m; i++) {
        for (int t = 0; t < a[i] / b[i]; t++)
            for (int j = n; j >= c[i]; j--)
                dp[j] = max(dp[j], dp[j - c[i]] + d[i]);
    }
    cout << dp[n] << endl;
    return 0;
}

以上都是本人刚学DP时做的一些题,当时代码可能写的比较乱,希望大家不要介意!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值