USACO22DEC G【杂题】

A. [USACO22DEC] Bribing Friends G

\(n\) 个朋友,第 \(i\) 个朋友有人气值 \(p_i\)。第 \(i\) 个朋友能出现,当且仅当你给它 \(c_i\) 块钱。如果你给第 \(i\) 个朋友 \(x_i\) 个冰淇淋,那么他会给你 \(1\) 块钱的折扣。现在你有 \(a\) 块钱和 \(b\) 个冰淇淋,问能出现的朋友人气之和的最大值。

\(n,p_i,c_i,x_i,a,b \leq 2 \times 10^3\)


假设已经确定了最后出现的朋友集合 \(S\) ,由于每个朋友都只会给 \(1\) 的折扣,那么我们分配冰淇淋的方案一定是:将 \(S\) 内的元素按照 \(x_i\) 从小到大排序,然后优先使用冰淇淋。换言之,一定存在一个分界点 \(i\),使得其之前的朋友都用冰淇淋,其之后的朋友都用钱。

于是可以设 \(f_{i,j}\) 表示前 \(i\) 个朋友,用 \(j\) 个冰淇淋得到的最大人气值和,\(g_{i,j}\) 表示 \(i\) 的后缀,用 \(j\) 块前得到的最大人气值和,转移就是一个背包。最后枚举分界点把两部分拼起来即可。时间复杂度 \(\mathcal{O}(n^2)\)

code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e3 + 5;
struct dat {
    int p, c, x;
} d[N];
int n, A, B;
int f[N][N], g[N][N];
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> A >> B;
    for (int i = 1; i <= n; i++) {
        cin >> d[i].p;
        cin >> d[i].c;
        cin >> d[i].x;
    }
    sort(d + 1, d + n + 1, [&](dat a, dat b){
        return a.x < b.x;
    });
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= B; j++) {
            f[i][j] = f[i - 1][j];
        }
        for (int j = d[i].x * d[i].c; j <= B; j++) {
            f[i][j] = max(f[i][j], f[i - 1][j - d[i].x * d[i].c] + d[i].p);
        }
    }
    for (int i = n; i >= 1; i--) {
        for (int j = 0; j <= A; j++) {
            g[i][j] = g[i + 1][j];
        }
        for (int j = d[i].c; j <= A; j++) {
            g[i][j] = max(g[i][j], g[i + 1][j - d[i].c] + d[i].p);
        }
    }
    int ans = 0;
    for (int i = 0; i <= n; i++) {
        ans = max(ans, f[i][B] + g[i + 1][A]);
        if (i == 0)
            continue;
        for (int j = 0; j <= min(A, d[i].c); j++) {
            if (d[i].x * (d[i].c - j) > B)
                continue;
            ans = max(ans, f[i - 1][B - d[i].x * (d[i].c - j)] + g[i + 1][A - j] + d[i].p);
        }
    }
    cout << ans << "\n";
    return 0;   
}

B. [USACO22DEC] Mountains G

\(n\) 座山,第 \(i\) 座山高度为 \(h_i\)。对于两座山 \(i,j(i<j)\),我们称它们能相互看见,当且仅当不存在某座山 \(k(i<k<j)\),使得在平面直角坐标系中 \((i,h_k)\) 位于连接 \((i,h_i),(j,h_j)\) 的线段之上。

\(q\) 次操作,每次增加某座山的高度,每次操作后求能相互看见的山的无序对个数。

\(n,q \leq 2 \times 10^3\)\(h_i \leq 10^9\)


先考虑如何求出 \(i\) 右边能和它互相看到的山。这比较简单,我们维护一个序列 \(s_i\),依次考虑 \(j\),如果 \(s_i\) 为空或 \(k(i,p) \leq k(i,j)\) 就将 \(j\) 加入 \(s_i\),其中 \(p\)\(s_i\) 的末尾元素,\(k(i,j)\) 表示连接 \((i,h_i),(j,h_j)\) 的线段的斜率。

因为允许 \(\mathcal{O}(n^2)\) 的复杂度,所以我们考虑对于每座山 \(i\) 维护序列 \(s_i\)。对于修改 \((x,y)\),我们重构 \(s_x\),然后对于 \(j < x\),由于 \(h_x\) 增加,则 \(k(j,x)\) 一定会增加,因此我们检查能否将 \(x\) 插入 \(s_j\),并且插入 \(s_j\) 后可能需要删除 \(x\) 之后的一段元素。上述操作容易通过 set 维护。

虽然看起来很暴力,但复杂度其实是对的:重构部分复杂度 \(\mathcal{O}(nq)\),初始时最多有 \(\mathcal{O}(n^2)\) 个元素,每次操作最多加入 \(\mathcal{O}(n)\) 个元素,它们最多被插入一次,删除一次,因此这一部分的总复杂度为 \(\mathcal{O}(n(n+q) \log n)\)

\(n,q\) 同阶,则总复杂度为 \(\mathcal{O}(n^2 \log n)\),可以通过本题。

code
#include <bits/stdc++.h>
#define fi first
#define se second
#define lob lower_bound
using namespace std;
const int N = 2e3 + 5, inf = 2e9;
typedef double db;
typedef pair <int, db> pi;
int n, q, a[N];
set <pi> v[N];
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            db k = (db)(a[j] - a[i]) / (j - i);
            if (v[i].empty() || (*prev(v[i].end())).se <= k) 
                v[i].emplace(j, k);
        }
    }
    cin >> q;
    for (int i = 1, x, y; i <= q; i++) {
        cin >> x >> y;
        a[x] += y;
        for (int j = 1; j < x; j++) {
            db k = (db)(a[x] - a[j]) / (x - j);
            bool ok = false;
            auto it = v[j].lob(pi(x, -inf));
            if (it != v[j].end() && it -> fi == x) {
                v[j].erase(it);
            }
            it = v[j].lob(pi(x, k));
            auto r = it;
            if (it == v[j].begin()) {
                r = v[j].emplace(pi(x, k)).fi;
                ok = true;
            } else if ((*(--it)).se <= k) {
                r = v[j].emplace(pi(x, k)).fi;
                ok = true;
            }
            if (ok == false)
                continue;
            auto le = next(r);
            auto ri = next(r);
            while (ri != v[j].end() && (*ri).se < k) {
                ri++;
            }
            v[j].erase(le, ri);
        }
        v[x].clear();
        for (int j = x + 1; j <= n; j++) {
            db k = (db)(a[j] - a[x]) / (j - x);
            if (v[x].empty() || (*prev(v[x].end())).se <= k) 
                v[x].emplace(j, k);
        }
        int ans = 0;
        for (int j = 1; j <= n; j++) {
            ans += v[j].size();
        }
        cout << ans << "\n";
    }
    return 0;   
}

C. [USACO22DEC] Strongest Friendship Group G

给定一个 \(n\) 个点 \(m\) 条边的无向图 \(G\),定义 \(G\) 的连通导出子图 \(S\) 的权值 \(f(S) = |S| \times \min_{i \in S} d_i\),其中 \(d_i\) 为点 \(i\)\(S\) 中的度数。求 \(f(S)\) 的最大值。

\(n \leq 10^5\)\(m \leq 2 \times 10^5\)


有两种思考的角度:枚举 \(|S|\) 或者枚举 \(\min_{i \in S} d_i\)。前者对简化问题并没有帮助,我们考虑枚举 \(\min_{i \in S} d_i\)

考虑 \(G\) 中度数最小的点 \(u\)(多个最小值则任取一个),显然包含 \(u\) 的极大联通子图 \(S\) 即为所有包含 \(u\) 的点集中 \(f(S)\) 最大的一个。由此不难得到以下做法:每次取出当前度数最小的点 \(u\),计算包含 \(u\) 的极大联通子图的 \(f\) 值,再将 \(u\) 及其相连的边删去,重复操作直到所有点被删完。

剩下的问题是如何维护这些操作。考虑到删边难以维护连通块大小,但我们可以先对每个点 \(u\) 求出枚举到 \(u\) 时剩余的度数 \(r_u\),然后倒过来加边,用并查集维护连通块大小即可。时间复杂度 \(O((n+m) \log n)\)

code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
#define pb push_back
#define mk make_pair
#define fi first
#define se second
typedef long long LL;
typedef pair <int, int> pi;
int n, m;
int f[N], sz[N], d[N], rd[N];
bool era[N];
vector <int> v, e[N];
priority_queue <pi, vector <pi>, greater <pi>> q;
int gf(int x) {
    return x == f[x] ? x : f[x] = gf(f[x]);
}
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m;
    for (int i = 1, x, y; i <= m; i++) {
        cin >> x >> y;
        e[x].pb(y), d[y]++;
        e[y].pb(x), d[x]++;
    }
    for (int i = 1; i <= n; i++)
        q.push(mk(d[i], i));
    for (int i = 1; i <= n; i++) {
        int u = q.top().se;
        q.pop();
        while (era[u]) {
            u = q.top().se;
            q.pop();
        }
        era[u] = true, rd[u] = d[u];
        v.pb(u);
        for (auto x : e[u]) {
            d[x]--;
            q.push(mk(d[x], x));
        }
    }
    for (int i = 1; i <= n; i++)
        f[i] = i, sz[i] = 1;
    LL ans = 0;
    reverse(v.begin(), v.end());
    for (auto u : v) {
        era[u] = false;
        for (auto x : e[u]) {
            if (era[x] == true)
                continue;
            int fu = gf(u), fv = gf(x);
            if (fu != fv) {
                f[fu] = fv, sz[fv] += sz[fu];
            }
        }
        ans = max(ans, 1LL * rd[u] * sz[gf(u)]);
    }
    cout << ans << "\n";
    return 0;   
}
posted @ 2023-01-05 15:10  came11ia  阅读(90)  评论(0编辑  收藏  举报