最近的刷题回顾--------DP-背包

只挑了一部分我觉得有点意义的题目

1.HDU-2159 FATE:http://acm.hdu.edu.cn/showproblem.php?pid=2159 

题意:刷怪升级,有k种怪,刷一只怪消耗bi忍耐值,获得ai经验, 升级需要n点经验值,还有m点忍耐值,并且自己最多刷s只                    怪,问是否能升级,以及如果能,最多还能剩多少忍耐值。(0<n,m,k,s <100,  0 < a, b < 20)

题解:  二维花费的完全背包,最简单的dp[i][j][k]表示: 到第i只怪为止,用j点忍耐,k只怪所获得的最大经验值.

          则有转移方程:dp[i][j][k] = max(dp[i-1][j][k],  dp[i][j-b[i]][k-1]+a[i])  表示对于第i只怪有两种策略:1.不刷,2.至少刷一只.

          然后发现可以取消第一维.注意第二种策略是包含了第i只怪对自身的影响,所以枚举j,k应该从小到大枚举.找答案时,从小到大            枚举j,只要存在一个dp[j][k] >= n && k <= s的,直接跳出.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e2+10;

#define fi first
#define se second

int dp[maxn][maxn];
int w[maxn], val[maxn];

int main() {
    std::ios::sync_with_stdio(false);
    int n, m, k, s;
    while(cin >> n >> m >> k >> s) {
        for(int i = 1; i <= k; i++)
            cin >> val[i] >> w[i];
        memset(dp, 0, sizeof dp);
        bool flag = false;  int ans = 200;
        for(int i = 1; i <= k; i++) {
            for(int j = w[i]; j <= m; j++) {
                for(int t = 1; t <= s; t++) {
                    dp[j][t] = max(dp[j][t], dp[j-w[i]][t-1]+val[i]);
                    if(dp[j][t] >= n)  ans = min(ans, j);
                }
            }
        }
        if(ans == 200)  cout << "-1\n";
        else cout << m-ans << "\n";
    }
    return 0;
}

 

2.HDU-2955  Robberies:http://acm.hdu.edu.cn/showproblem.php?pid=2955

题意: 有n个银行,抢劫第i个银行可以mi的钱,但有pi的风险被抓住, 每个银行只能抢一次,问要在被抓风险不超过P的情况下,             能抢到的最多的钱数是多少。(n <=  100, mi <= 100)

题解:按照裸01背包的想法应该是,dp[i]表示被抓风险不超过i的情况下所能获取最大钱数。但是会发现两个问题:1.对于若干个             银行,求被抓的概率不好算。2.概率是小数,没办法遍历。所以转换思想,对于"被抓风险不超过P", 转换成"不被抓风险               超过1-P",其次,将dp[i]: 设置为获取i金钱,不被抓风险最大为dp[i].  然后就是熟悉的01背包了。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4+10;

double dp[maxn];
pair<int, double> a[maxn];
#define fi first
#define se second

int main() {
    std::ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while(T--) {
        double p;  int n;
        cin >> p >> n;
        p = 1-p;
        for(int i = 0; i < maxn; i++)  dp[i] = 0;
        int sum = 0;
        for(int i = 1; i <= n; i++) {
            cin >> a[i].fi >> a[i].se;
            a[i].se = 1-a[i].se;
            sum += a[i].fi;
        }
        dp[0] = 1;
        for(int i = 1; i <= n; i++) {
            for(int j = sum; j >= a[i].fi; j--) {
                dp[j] = max(dp[j], dp[j-a[i].fi]*a[i].se);
            }
        }
        int res = 0;
        for(int i = maxn-1; i >= 0; i--)
            if(dp[i] >= p)  {
                res = i;
                break;
            }
        cout << res << "\n";
    }
    return 0;
}

 

3. HDU-2191 http://acm.hdu.edu.cn/showproblem.php?pid=2191

题意: 有资金n元,市场有m中大米,大米只能按袋卖,给出每种大米的  每袋价格p,每袋重量h,袋数c,问最多能买多重的大米。(1 <= n, m <= 100,   1 <= p <= 20, 1 <= h <= 200, 1 <= c <= 20).

题解: 裸的多重背包.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e2+10;

vector<pair<int, int> > a;
#define fi first
#define se second
int dp[maxn];

void solve(int p, int h, int c) {
    int now = 1;
    while(c) {
        if(c < now) now = c;
        a.push_back(make_pair(now*p, now*h));
        c -= now;
        now <<= 1;
    }
}

int main() {
    std::ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while(T--) {
        a.clear();
        int n, m;
        cin >> n >> m;
        for(int i = 0; i < m; i++) {
            int p, h, c;
            cin >> p >> h >> c;
            solve(p, h, c);
        }
        memset(dp, 0, sizeof dp);
        for(int i = 0; i < a.size(); i++) {
            for(int j = n; j >= a[i].fi; j--) {
                dp[j] = max(dp[j], dp[j-a[i].fi]+a[i].se);
            }
        }
        int res = 0;
        for(int i = 0; i <= n; i++)  res = max(res, dp[i]);
        cout << res << "\n";
    }
    return 0;
}

 

4.HDU-1171 http://acm.hdu.edu.cn/showproblem.php?pid=1171

题意:有n中机器,每种机器有自己的价值V以及数量M,让你将这些机器分成A, B两部分,使得两部分的价值和的差值尽可能小,且A的价值和 >= B的价值和。输出两部分的价值和.

题解:  由于A >= B所以,B <= sum/2, 将问题转换成 用sum/2的资源去换取商品,求能获得商品价值的最大是多少. 转换后就是             裸的多重背包。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5;

int dp[maxn], a[maxn], cnt, sum;

void solve(int val, int num) {
    int now = 1;
    while(num) {
        if(num < now)  now = num;
        a[cnt++] = val*now;
        num -= now;  now <<= 1;
    }
}

int main() {
    int n;
    while(scanf("%d", &n) != EOF) {
        if(n < 0)  break;
        cnt = 0, sum = 0;
        for(int i = 1; i <= n; i++) {
            int val, num;
            scanf("%d%d", &val, &num);
            solve(val, num);
            sum += val*num;
        }
        int st = sum/2, ans = 0;
        memset(dp, 0, sizeof dp);
        for(int i = 0; i < cnt; i++) {
            for(int j = st; j >= a[i]; j--) {
                dp[j] = max(dp[j], dp[j-a[i]]+a[i]);
            }
        }
        ans = dp[st];
        printf("%d %d\n", sum-ans, ans);
    }
    return 0;
}

5.HDU-2639 http://acm.hdu.edu.cn/showproblem.php?pid=2639

题意: 有一个空间为V的背包,有N种骨头,每种骨头有自己的价值与体积,给定一个K求所有装取骨头的方案中,价值第K大的是多少。两种方案的价值和一样则认为是一种方案。 k <= 30, n <= 100, v <= 1000

题解: 和01背包没太大区别,在01背包基础上增加一维dp[i][j]表示: 到目前为止用 i 空间,所能获取价值第j大是多少.  每次枚举i时,将dp[i][1]----dp[i][k] k种方案分别尝试增加当前这个骨头以及不增加,然后去重就好了.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e3+10;

int dp[maxn][31], val[maxn], w[maxn];

int main() {
    std::ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while(T--) {
        int n, m, k;
        cin >> n >> m >> k;
        for(int i = 1; i <= n; i++)  cin >> val[i];
        for(int i = 1; i <= n; i++)  cin >> w[i];
        memset(dp, 0, sizeof dp);
        vector<int> tmp;
        for(int i = 1; i <= n; i++) {
            for(int j = m; j >= w[i]; j--) {
                tmp.clear();
                for(int t = 1; t <= k; t++) {
                    tmp.push_back(dp[j][t]);
                    tmp.push_back(dp[j-w[i]][t]+val[i]);
                }
                sort(tmp.begin(), tmp.end());
                int t = tmp.size()-1, now = 1;
                while(t >= 0 && now <= k) {
                    dp[j][now] = tmp[t];
                    if(now == 1 || dp[j][now] != dp[j][now-1])  now++;
                    t--;
                }
            }
        }
       /* for(int i = 1; i <= k; i++)
            cout << dp[m][i] << endl;*/
        cout << dp[m][k] << "\n";
    }
}

6. codeforces 544 http://codeforces.com/contest/544/problem/C

题意: n个人,有m行代码要写,第i个人 写一行会导致ai个bug,现在要把这m行代码分给n个人,每个人分到的可以为0, 要求总             bug小于等于b.方案数取模.  (1 <= n, m <= 500, 0 <= b <= 500)

题解: dp[i][j][k]表示前i个人 bug数为j,写了k行的方案数.  对于当前第i个人考虑 要么不分,要么至少一行,则dp[i][j][k] = dp[i-1][j][k]+dp[i][j-a[i]][k-1];  然后可以把第一维滚动掉.

#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e2+10;
 
int dp[2][maxn][maxn], a[maxn];
 
int main() {
    std::ios::sync_with_stdio(false);
    int n, m, b, mod;
    cin >> n >> m >> b >> mod;
    for(int i = 1; i <= n; i++)
        cin >> a[i];
    int last = 1, now = 0;
    dp[now][0][0] = 1;
    for(int i = 1; i <= n; i++) {
        swap(last, now);
        dp[now][0][0] = 1;
        for(int j = 1; j <= m; j++) {
            for(int k = 0; k <= b; k++) {
                if(k < a[i])  dp[now][j][k] = dp[last][j][k];
                else dp[now][j][k] = (dp[last][j][k]+dp[now][j-1][k-a[i]])%mod;
            }
        }
    }
    int res = 0;
    for(int i = 0; i <= b; i++)
        res = (res+dp[now][m][i])%mod;
    cout << res << "\n";
    return 0;
}

7.codeforces 864E http://codeforces.com/contest/864/problem/E

题意:有n个文件即将被火烧,每个文件有三个属性: 拯救该文件所需时间ti  被火烧掉的时间点di  价值pi,问最多能拯救多少价值和的文件,并输出价值和   文件个数   拯救顺序。

题解:按照di排序  01背包  更新时记录一下当前文件id以及上一个文件id

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e3+10;
const int maxx = 1e2+10;
 
struct Node {
    int x, y, val, id;
    bool operator <(const Node &a) const {
        return val < a.val;
    }
};
 
Node dp[maxx][maxn];
 
struct node {
    int t, d, p, id; // cost  deadline value id
    bool operator <(const node &a) const {
        return d < a.d;
    }
}a[maxx];
 
int main() {
    std::ios::sync_with_stdio(false);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)  {
        cin >> a[i].t >> a[i].d >> a[i].p;
        a[i].id = i;
    }
    sort(a+1, a+1+n);
    for(int i = 1; i <= n; i++) {
        for(int j = 0; j < maxn; j++) {
            if(j >= a[i].t && j < a[i].d && dp[i-1][j].val < (dp[i-1][j-a[i].t].val+a[i].p)) {
                dp[i][j] = (Node){i-1, j-a[i].t, dp[i-1][j-a[i].t].val+a[i].p, a[i].id};
            }
            else dp[i][j] = dp[i-1][j];
        }
    }
    Node res = dp[n][0];
    for(int i = 1; i < maxn; i++)
        if(res < dp[n][i])  res = dp[n][i];
    vector<int> ans;
    cout << res.val << "\n";
    while(res.id != 0) {
        ans.push_back(res.id);
        res = dp[res.x][res.y];
    }
    cout << ans.size() << "\n";
    reverse(ans.begin(), ans.end());
    for(int i : ans)
        cout << i << " ";
    return 0;
}

8. codeforces 730J  http://codeforces.com/contest/730/problem/J

题意:有n个杯子,每个杯子体积为ai,当前杯子里有bi的水,现要求把所有的水倒入尽可能少的杯子里,移动一体积的水需要一分钟,求最少的杯子数量,以及在该数量下的最少时间。(1<=n<=100,  1<=bi <= ai <= 100)

题解:首先最少的杯子数量好求,贪心拿体积最大的杯子就行,  那我们把杯子体积看作价值,已有的水,和杯子的数量看作花费

           

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e4+10;
 
struct node {
    int a, b;
}p[105];
 
bool cmp_b(node x, node y) {
    return x.b > y.b;
}
 
int dp[105][maxn], sum_b[maxn], sum_a[maxn];
 
int f(int len, int sum) {
    int l = 1, r = len, ans = 0;
    while(l <= r) {
        int mid = (l+r)>>1;
        if(sum_b[mid] >= sum)  ans = mid, r = mid-1;
        else l = mid+1;
    }
    return ans;
}
 
int main() {
    std::ios::sync_with_stdio(false);
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++) cin >> p[i].a;
    for(int i = 1; i <= n; i++) cin >> p[i].b;
    sort(p+1, p+1+n, cmp_b);
    sum_b[0] = 0, sum_a[0] = 0;
    for(int i = 1; i <= n; i++)  {
        sum_b[i] = sum_b[i-1]+p[i].b;
        sum_a[i] = sum_a[i-1]+p[i].a;
    }
    int kk = f(n, sum_a[n]);
 
    memset(dp, -1, sizeof dp);
    dp[0][0] = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = sum_a[i]; j >= p[i].a; j--) {
            for(int k = kk; k >= 1; k--)
                if(dp[k-1][j-p[i].a] != -1) dp[k][j] = max(dp[k][j], dp[k-1][j-p[i].a]+p[i].b);
        }
    }
 
    int ans;
    for(int i = sum_a[n]; i >= 0; i--)
        if(dp[kk][i] >= sum_a[n]) {
            ans = sum_a[n]-i;
            break;
        }
    cout << kk << " " << ans << "\n";
    return 0;
}
 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值