2021-08-21一期17&二期15暑假集訓
阿新 • • 發佈:2021-08-23
[2018國慶雅禮集訓10-5T2]賽
難度 :
思維難度 : 2星
程式碼難度 : 3星
標籤:
\(wqs\) 二分 + 貪心
題解 :
先考慮沒有 \(m\) 的限制,容易得到一種貪心策略,兩個人得到的物品數是齊頭並進的,即在某一次貪心中,要麼兩個人各拿一個自己喜歡的東西,要麼拿一個兩個人都喜歡的東西,容易證明這樣是對的.
考慮加上 \(m\) 的限制,因為每次拿物品時,拿到的物品的價值一定大於等於上一次拿到的物品的價值. 所以價值和關於 \(m\) 是一個凸函式,可以用 \(wqs\) 二分解決。
但是要注意 \(wqs\) 二分後物品的價值有可能是負數,因為負數是一定要被選的,所以貪心時要把它的價值視為 \(0\)
Code:
#include <iostream> #include <cstdio> #include <vector> #include <algorithm> #define int long long using namespace std; const int N = 3e5 + 10; int n, m, k; int v[N]; int cnta, cntb; int isa[N], isb[N]; int a[N], b[N], ab[N], el[N]; int tota, totb, totab, totel; int res; int inf = 0x3f3f3f3f3f3f3f3f; vector<int> w; int check(int w) { int tmp = k; int ans = 0; int cho = 0; for (int i = 1; i <= totab; ++i) { ab[i] -= w; } for (int i = 1; i <= tota; ++i) { a[i] -= w; } for (int i = 1; i <= totb; ++i) { b[i] -= w; } for (int i = 1; i <= totel; ++i) { el[i] -= w; } int i = 1, j = 1; while (tmp--) { if (i == min(tota, totb) + 1 && j == totab + 1) { puts("-1"); exit(0); } if ((i <= min(tota, totb) && (a[i] > 0 ? a[i] : 0) + (b[i] > 0 ? b[i] : 0) <= (ab[j] > 0 ? ab[j] : 0)) || j > totab) ans += a[i] + b[i], ++i, cho += 2; else ans += ab[j], ++j, cho += 1; } int x = i, y = j; for (int i = x; i <= tota; ++i) { if (a[i] <= 0) ans += a[i], cho += 1; } for (int i = x; i <= totb; ++i) { if (b[i] <= 0) ans += b[i], cho += 1; } for (int i = y; i <= totab; ++i) { if (ab[i] <= 0) ans += ab[i], cho += 1; } for (int i = 1; i <= totel; ++i) { if (el[i] <= 0) ans += el[i], cho += 1; } for (int i = 1; i <= totab; ++i) { ab[i] += w; } for (int i = 1; i <= tota; ++i) { a[i] += w; } for (int i = 1; i <= totb; ++i) { b[i] += w; } for (int i = 1; i <= totel; ++i) { el[i] += w; } res = ans; return cho; } signed main() { scanf("%lld%lld%lld", &n, &m, &k); for (int i = 1; i <= n; ++i) { scanf("%lld", &v[i]); } scanf("%lld", &cnta); for (int i = 1; i <= cnta; ++i) { int x; scanf("%lld", &x); isa[x] = 1; } scanf("%lld", &cntb); for (int i = 1; i <= cntb; ++i) { int x; scanf("%lld", &x); isb[x] = 1; } if (m < k) return puts("-1"), 0; for (int i = 1; i <= n; ++i) { if (isa[i] && isb[i]) ab[++totab] = v[i]; else if (isa[i]) a[++tota] = v[i]; else if (isb[i]) b[++totb] = v[i]; else el[++totel] = v[i]; } sort(ab + 1, ab + totab + 1); sort(a + 1, a + tota + 1); sort(b + 1, b + totb + 1); sort(el + 1, el + totel + 1); if (m < k) return puts("-1"), 0; if (m < 2 * k - totab) return puts("-1"), 0; if (tota + totab < k || totb + totab < k) return puts("-1"), 0; int l = -2000000000, r = 2000000000; while (l < r) { int mid = (l + r) >> 1; if (check(mid) >= m) r = mid; else l = mid + 1; } int tmp = check(l); cout << res + l * m << endl; return 0; }
總結
一道小貪心,但原題細節比較多,也是一道 \(wqs\) 的套路題.
[2018國慶雅禮集訓10-6T1]Merchant
難度 :
思維難度 : 2星
程式碼難度 : 2星
標籤:
二分 + STL
題解:
首先想到二分,然後發現二分不滿足單調性。但是總價值關於 \(t\) 滿足斜率單調遞增,所以這是一個凸函式。先二分找到最低點,從最低點向右是一個遞增函式,可以二分答案,最低點左邊是一個遞減函式,也可以二分答案.
其實沒有這麼麻煩,因為最低點左邊最高點一定是 \(0\), 先特判掉,然後如果 \(0\) 不滿足條件,那麼從 \(0\) 到最低點都不滿足條件,從最低點向右有是一個遞增函式,有單調性 , 所以特判零之後直接二分就行.
Code:
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
const int N = 2e6 + 10;
int n, m, s;
int ans = 0;
struct OB {
int k, b, val;
bool operator < (const OB A) const {
if (val == A.val) {
if (k == A.k) {
return b < A.b;
}
return k < A.k;
}
return val > A.val;
}
}a[N];
int check1(int t) {
int res = 0;
for (int i = 1; i <= n; ++i) {
a[i].val = a[i].k * t + a[i].b;
}
nth_element(a + 1, a + m, a + n + 1);
for (int i = 1; i <= m; ++i) {
if (a[i].val > 0) res += a[i].k;
}
return res;
}
int check2(int t) {
int res = 0;
for (int i = 1; i <= n; ++i) {
a[i].val = a[i].k * t + a[i].b;
}
nth_element(a + 1, a + m, a + n + 1);
for (int i = 1; i <= m; ++i) {
if (a[i].val > 0) res += a[i].val;
if (res >= s) return 1;
}
return 0;
}
signed main() {
scanf("%lld%lld%lld", &n, &m, &s);
for (int i = 1; i <= n; ++i) {
scanf("%lld%lld", &a[i].k, &a[i].b);
}
int l = 0, r = 1000000000;
while (l < r) {
int mid = (l + r) >> 1;
if (check1(mid) >= 0) r = mid;
else l = mid + 1;
}
if (check2(0)) {
puts("0");
return 0;
}
l = l, r = 1000000000;
while (l < r) {
int mid = (l + r) >> 1;
if (check2(mid)) r = mid;
else l = mid + 1;
}
printf("%lld\n", l);
return 0;
}
總結
\(nth\_element\) 大法好。
[2018國慶雅禮集訓10-6T2]Equation
難度
思維難度 : 3星
程式碼難度 : 3星
標籤
\(dfs\) 序 + 樹狀陣列
題解 :
11