1. 程式人生 > 其它 >2021-08-21一期17&二期15暑假集訓

2021-08-21一期17&二期15暑假集訓

[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