Codeforces Round #697 (Div. 3) D. Cleaning the Phone 的两种做法(背包问题的变种题)

题目

题意是给定一个n和m(n是应用程序个数,m是至少要释放的内存空间),ai是第i个应用程序所占的内存空间,bi是第i个应用程序的convenience points,问释放至少m的内存空间损失的最小convenience points.(convenience points的值是1或者2)

法一.贪心+二分

因为convenience points的值只能是1或2,用ma数组存bi是1的应用程序的ai,用mb数组存bi是2的应用程序的ai,若我们要选择pa个a数组里的数和pb个b数组里的数,显然选择内存空间越大的越好,所以给a和b排个序,求前缀和,那么pa个a数组里的数对应的最大内存空间就是a的前缀和,因为前缀和数组单调递增,所以就可以二分查找mb的前缀和求出刚好满足内存空间之和大于m的pb,于是当前pa的最小convenience points就是pa+2*pb,时间复杂度 O(nlogn).

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
    ll Case;
    scanf("%lld", &Case);
    while (Case--) {
        ll n, m;
        scanf("%lld%lld", &n, &m);
        vector<ll>temp(n + 1);
        vector<ll> a, b;
        for (ll i = 0; i < n; i++) scanf("%lld", &temp[i]);
        for (ll i = 0; i < n; i++) {
            ll x;
            scanf("%lld", &x);
            if (x == 1) a.push_back(temp[i]);
            else b.push_back(temp[i]);
        }
        ll na = a.size(), nb = b.size();
        sort(a.rbegin(), a.rend());
        sort(b.rbegin(), b.rend());
        vector<ll>pre(nb);
        if(b.size()) pre[0] = b[0];
        for (ll i = 1; i < nb; i++) pre[i] = pre[i - 1] + b[i];
        ll sum = 0, ans = 1e9;
        if (lower_bound(pre.begin(), pre.end(), m) != pre.end()) {
            ans = (lower_bound(pre.begin(), pre.end(), m)-pre.begin()) * 2 + 2;
        }
        for (ll i = 0; i < na; i++) {
            sum += a[i];
            ll t = i + 1;
            if (sum >= m) { ans = min(t, ans); break; }
            ll x = m - sum;
            auto p = lower_bound(pre.begin(), pre.end(), x);
            if (p != pre.end()) {
                ll s = p - pre.begin();
                ans = min(ans, t + s * 2 + 2);
            }
        }
        if (ans != 1e9)
            printf("%lld\n", ans);
        else puts("-1");
    }
    return 0;
}

 法二.贪心+01背包

题目意思是求选若干个应用程序,选出来的应用程序的内存空间大于等于m的应用程序的convenience points的最小值.我们不妨反过来考虑,假设所有应用程序的内存空间之和是allme,所有convenience points之和是allpo,那么题目等价于选出内存空间小于m的应用程序convenience points的最大值。但是由于题目m的范围很大,所以单纯的01背包解决不了这个问题。但是convenience points之和小于4e5,所以我们可以把convenience points当成体积,内存空间看成价值,然后二分从0到4e5的convenience points,找到满足条件的convenience points的最大值即可。f[i]的含义是convenience points刚好是i的内存空间的最小值。贪心把分数拿到5以下再用01背包可行,证明:

设分数是P,设ai是convenience points为1所占的内存空间,bi是convenience points为2所占的内存空间,若b0/2<=b1/2<=...<=bj/2<=a0<=a1<=a2<=..aia0<=a1<=a2<=..ai<=b0/2<=b1/2<=...<=bj/2,则显然选择越小的越好若有...<=b0/2<=a0<=b1/2<=...或者...<=a0<=

b0/2<=b1/2<=a1<=... ,那么当P大于6的时候,显然可以将这四个数取尽,当p小于6时,则有更加复杂的结构,于是使用01背包求最小值,时间复杂度O(nlogn).

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 5;
long long f[15];
int n, m;
long long allpo = 0, allme = 0;
struct my {
    int a, b;
    double sum;
    bool operator<(const my& T) {
        return sum < T.sum;
    }
}tr[N];
bool check(int p) {
    int idx = 0;
    long long sum = 0;
    while (p > 6 && idx < n) {
        p -= tr[idx].b;
        sum += tr[idx].a;
        idx++;
    }
    if (p > 6) if (allme - sum >= m) return 1;
    else
    return 0;
    memset(f, 0xcf, sizeof f);
    f[0] = 0;
    for (int i = idx; i < n; i++)
        for (int j = p+2; j >= tr[i].b; j--)
            f[j] = max(f[j], f[j - tr[i].b] - tr[i].a);
    if (allme + max(f[p],max(f[p+1],f[p+2])) - sum >= m) return 1;
    return 0;
}
void solve() {
    scanf("%d%d", &n, &m);
    allpo = 0, allme = 0;
    for (int i = 0; i < n; i++) scanf("%d", &tr[i].a), allme += tr[i].a;
    for (int i = 0; i < n; i++) {
        scanf("%d", &tr[i].b);
        tr[i].sum = (double)tr[i].a / tr[i].b;
        allpo += tr[i].b;
    }
    sort(tr, tr + n);
    int l = 0, r = 4e5;
    while (l < r) {
        int mid = (l + r + 1) >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    if (check(r))
        printf("%lld\n", allpo - (long long)r);
    else puts("-1");
}
int main() {
    int Case;
    scanf("%d", &Case);
    while (Case--) solve();
    return 0;
}

 

posted @ 2021-01-27 01:26  liuzhexuan  阅读(38)  评论(0)    收藏  举报