P3045 【USACO12FEB】 Cow Coupons G

P3045 USACO12FEB Cow Coupons G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
提供两种解法,反悔贪心和 wqs 二分套二分。

根据题意我们需要求出选 \([1, n]\) 个牛的最小价值,且花费券不超过 \(k\)。一个明显的贪心是有券1
肯定用券(\(K\le N\))。所以花费券一定是 \(k\) 张。

反悔贪心

很容易想到 DP 或贪心,普通 DP 看范围肯定是不行了,尝试贪心。

正常的贪心是先用优惠券买,再原价买。当然这比较容易推出是错的。如:

2 1 4
2 1
10 2

根据流程会选 \(1,10\),得出只能买一头牛,然而更优的是选两个 \(2\),可以买两头牛。

所以优惠券的优惠力度没有利用完全。考虑反悔,采用替代的方式让这个券给别的牛用。但这样没有券的消耗,那么我们先用完 k 张券,即先用券卖完,易知这些肯定是当前最小的。对于后 \(n - k\) 个因为已经没券了,所以要么直接买,要么替换券买(即 \(c[i]+p[j]-c[j]\)\(j\) 为被替换的牛)。用三个堆分别维护 \(p[i],c[i],p[i]-c[i]\) 即可。时间复杂度 \(O(n\log n)\).

在过程中判断最小值是否大于 \(m\) 如果大于说明已经买不了第 \(i\) 个牛了。输出 \(i - 1\)

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

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 50010;

LL n, m, k;
int p[N], c[N];
int st[N];

priority_queue<PII, vector<PII>, greater<PII>> q, d, q2;

int main()
{
    cin >> n >> k >> m;
    
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d%d", &p[i], &c[i]);
        q.push({c[i], i});
        q2.push({p[i], i});
    }
    
    LL ans = 0;
    for (int i = 1; i <= k; i ++ )
    {
        PII t = q.top();
        q.pop();
        ans += t.x;
        st[t.y] = 2;
        d.push({p[t.y] - c[t.y], t.y});
        if (ans > m) 
        {
            cout << i - 1 << endl;
            return 0;
        }
    }
    for (int i = k + 1; i <= n; i ++ )
    {
        while (st[q2.top().y]) q2.pop();
        while (st[q.top().y]) q.pop();
        while (st[d.top().y] != 2) d.pop();
        
        if (q2.top().x >= q.top().x + d.top().x)
        {
            PII t = q.top();
            ans += t.x + d.top().x;
            st[t.y] = 2;
            st[d.top().y] = 1;
            q.pop();
            d.pop();
            d.push({p[t.y] - c[t.y], t.y});
        }
        else
        {
            PII t = q2.top();
            ans += t.x;
            st[t.y] = 1;
            q2.pop();
        }
        
        if (ans > m)
        {
            cout << i - 1 << endl;
            return 0;
        }
        
    }
    
    cout << n << endl;
    
    return 0;
}

wqs 二分套二分

这种方法比较稳,也好想。对于能买多少牛,可以二分验证答案,即把问题转化为:买 \(x\) 头牛在恰好花费 \(k\) 张券下最小花费为多少。 这就是一个很典型的 wqs 二分问题了。

对于 check 函数是可以用贪心优化成 \(O(n)\)(这点请自己想)。二分加 wqs 二分是 \(O(\log n\log v),\max v = 10^9\),总体时间复杂度 \(O(n\log n \log v)\)。可以通过。

实际上效率比反悔贪心差不了太多(\(n\) 比较小)。

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

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<int, int> PII;

const int N = 50010;

LL n, m, k;
LL ans, cnt;
bool st[N];
PII p[N], c[N];

struct Node 
{
    int p, c;
    bool operator<(const Node &W)const
    {
        return c < W.c;
    }
}g[N];

void check1(int x, int n)
{
    ans = cnt = 0;
    int t1 = 1, t2 = 1;
    memset(st, 0, sizeof st);
    
    for (int i = 1; i <= n; i ++ )
    {
        while (st[c[t2].y]) t2 ++ ;
        while (st[p[t1].y]) t1 ++ ;
        
        if (p[t1].x >= c[t2].x - x) 
        {
            cnt ++ ;
            st[c[t2].y] = true;
            ans += c[t2 ++ ].x - x;
        }
        else 
        {
            st[p[t1].y] = true;
            ans += p[t1 ++ ].x;
        }
    }
}

bool check(int x)
{
    int l = -(1e9 + 10), r = 0; 
    while (l < r)
    {
        int mid = l + r >> 1;
        check1(mid, x);
        if (cnt >= k) r = mid;
        else l = mid + 1;
    }
    check1(l, x);
    
    // cout << ans + l * k << endl;
    return ans + l * k <= m;
}

int main()
{
    cin >> n >> k >>  m;
    for (int i = 1; i <= n; i ++ ) 
    {
        int a, b;
        scanf("%d%d", &a, &b);
        p[i] = {a, i};
        c[i] = {b, i};
    }
    
    sort(p + 1, p + 1 + n);
    sort(c + 1, c + 1 + n);
    
    int l = 0, r = n; 
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    
    cout << l << endl;
    return 0;
}
posted @ 2024-11-26 17:02  blind5883  阅读(2)  评论(0编辑  收藏  举报