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;
}