USACO22DEC S【杂题】

A. [USACO22DEC] Barn Tree S

给定一颗树,初始时点 \(i\) 有权值 \(h_i\)。你可以进行若干次操作,每次选择有边直接相连的两个节点 \((u,v)\) 和一个整数 \(k\),令 \(h_u \gets h_u - k\)\(h_v \gets h_v + k\)。你需要保证操作过程中 \(h_i \geq 0\)

求使得所有 \(h_i\) 均相同的最小操作数,并构造方案。

\(n \leq 10^5\)\(h_i \leq 10^9\),输入保证有解。


先求出平均值 \(t = \frac{\sum a_i}{n}\),我们的目标就是让所有 \(h_i = t\)

对于一个子树 \(u\),设其中有 \(s_u\) 个点,子树中 \(h_i\) 的和为 \(d_u\)。若 \(s_u \times t < d_u\),那么说明这个子树内的 \(h_i\) 是不够自给自足的,必须从 \(f_u\) 处补上一部分(\(f_u\)\(u\) 的父亲),我们连边 \(f_u \to u\)。同理,若 \(s_u \times t > d_u\),则连边 \(u \to f_u\)。边的权值即为 \(|s_u \times t - d_u|\)

然后跑拓扑排序,依次执行操作就行了。时间复杂度 \(\mathcal{O}(n)\)

code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 2e5 + 5;
typedef long long LL;
typedef pair <int, int> pi;
int n, a[N], d[N], h[N], cnt;
LL t, s[N];
queue <int> q;
vector <array <LL, 3>> ans;
vector <int> e[N];
vector <pi> g[N];
void dfs(int u, int f) {
    for (auto v : e[u]) {
        if (v == f)
            continue;
        dfs(v, u);
        s[u] += s[v], h[u] += h[v];
    }
    s[u] += a[u];
    h[u] += 1;
    if (1LL * h[u] * t != s[u]) {
        cnt++;
    }
    if (1LL * h[u] * t < s[u]) {
        g[u].pb({f, 0});
        d[f]++;
    } else if (1LL * h[u] * t > s[u]) {
        g[f].pb({u, 1});
        d[u]++;
    }
}
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i], t += a[i];
    }
    for (int i = 1, u, v; i < n; i++) {
        cin >> u >> v;
        e[u].pb(v), e[v].pb(u);
    }
    t /= n;
    dfs(1, 0);
    for (int i = 1; i <= n; i++) 
        if (d[i] == 0) q.push(i);
    while (!q.empty()) {
        int u = q.front(); 
        q.pop();
        for (auto [v, k] : g[u]) {
            if (k == 0) {
                ans.pb({u, v, 1LL * s[u] - h[u] * t});
            } else {
                ans.pb({u, v, 1LL * h[v] * t - s[v]});
            }
            if (--d[v] == 0)
                q.push(v);
        }
    }
    cout << cnt << "\n";
    for (auto [x, y, w] : ans)
        cout << x << " " << y << " " << w << "\n";
    return 0;   
}

B. [USACO22DEC] Circular Barn S

长度为 \(n\) 的环形纸带,第 \(i\) 个格子上初始有数字 \(a_i\)。每轮 Alice 和 Bob 各执行一次操作,然后移动到下一个格子。每次操作可以选择一个不大于 \(a_i\) 的整数 \(p\),其中 \(p\)\(1\) 或一个质数,然后令 \(a_i \gets a_i - p\)

无法操作者(即 \(a_i = 0\) 时)败。给定初始状态,判断胜者。多测。

\(n \leq 10^5\)\(a_i \leq 5 \times 10^6\)


不难发现每个格子其实是独立的,全局的胜利只和每个独立问题必胜一方胜利的最大操作数有关。

先给出结论:对于独立的问题,当且仅当 \(a_i = 4n\) 时先手必败。证明考虑归纳:\(0\) 必败;若 \(0 \sim 4i\) 必败,则 \(4i + 1 \sim 4i + 3\) 必胜,\(4(i+1)\) 必败。

\(d_i\) 为必胜一方胜利所需步数,显然有 \(d_0 = 0\)\(d_p=1(p \in \text{prime})\)\(d_{4i+2} = d_{4i}+1\),且 \(d_{4i} = \max(d_{4i - 1},d_{4i - 2},d_{4i - 3})+1 = d_{4i - 2} + 1\)。由此可得对于偶数有 \(d_{i} = \frac{i}{2}\)。对于奇数,我们需要找到首个可以合法转移到的必败态。打表可以发现这个必败态不会很大,直接枚举即可。

时间复杂度 \(\mathcal{O}(V + n \log n)\)

code
#include <bits/stdc++.h>
#define pb push_back
using namespace std;
const int N = 1e5 + 5, M = 5e6 + 5;
struct dat {
    int a, b;
} d[N];
int t, n;
int cnt, p[N], f[M];
bool b[M];
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    int lim = 5e6;
    for (int i = 2; i <= lim; i++) {
        if (b[i] == false) {
            p[++cnt] = i;
        }
        for (int j = 1; j <= cnt && i * p[j] <= lim; j++) {
            b[i * p[j]] = true;
            if (i % p[j] == 0)
                break;
        }
    }
    for (int i = 1; i <= lim; i++) {
        if (i % 2 == 0) {
            f[i] = i / 2;
        } else {
            for (int k = 0; 4 * k < i; k++) {
                if (b[i - 4 * k] == true)
                    continue; 
                f[i] = 2 * k + 1;
                break;
            }
        }
    }
    cin >> t;
    while (t--) {
        cin >> n;
        for (int i = 1, x; i <= n; i++) {
            cin >> x;
            d[i] = (dat){f[x], i};
        }
        sort(d + 1, d + n + 1, [&](dat x, dat y) {
            if (x.a / 2 == y.a / 2) {
                return x.b < y.b;
            } else {
                return x.a / 2 < y.a / 2;
            }
        });
        if (d[1].a % 2 == 0) {
            cout << "Farmer Nhoj" << "\n";
        } else {
            cout << "Farmer John" << "\n";
        }
    }
    return 0;   
}

C. [USACO22DEC] Range Reconstruction S

给定一个 \(n \times n\) 的二维数组 \(r_{i,j}\),你需要构造一个长度为 \(n\) 的数组 \(a_i\),使得 \(-10^9 \leq a_i \leq 10^9\),且对于 \(i \leq j\)\(r_{i,j} = \max \{a_{i \cdots j} \} - \min \{ a_{i \cdots j}\}\)

\(n \leq 300\),保证存在某个 \(0 \leq b_i \leq 10^9\) 的数组 \(b\) 满足条件。


其实不需要用到那么多 \(r\)。如果我们确定了 \(a_i\)\(a_{i - 1}\),根据 \(r_{i,i+1}\)\(r_{i-1,i+1}\) 就可以推出 \(a_{i+1}\)

但你会发现当 \(a_i = a_{i-1}\) 时这个做法会出问题,此时 \(a_{i+1}\) 会有两种可能的取值。解决的办法也很简单,找到最大的 \(p\) 使得 \(a_{p-1} \neq a_p\),就能按上面的办法算了。

时间复杂度 \(\mathcal{O}(n^2)\),瓶颈在于读入。

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 3e2 + 5;
const LL inf = 1e18;
int n, p;
LL r[N][N], ans[N];
int main() {
    ios :: sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++)
        for (int j = i; j <= n; j++)
            cin >> r[i][j];
    ans[1] = 0;
    ans[2] = ans[1] + r[1][2];
    p = 2;
    for (int i = 3; i <= n; i++) {
        if (r[p - 1][i] == r[p - 1][p] + r[p][i]) {
            if (ans[p] < ans[p - 1]) {
                ans[i] = ans[p] - r[p][i];
            } else {
                ans[i] = ans[p] + r[p][i];
            }
        } else {
            if (ans[p] < ans[p - 1]) {
                ans[i] = ans[p] + r[p][i];
            } else {
                ans[i] = ans[p] - r[p][i];
            }
        }
        if (r[i - 1][i] != 0)
            p = i;
    }
    LL mi = inf;
    for (int i = 1; i <= n; i++)
        mi = min(mi, ans[i]);
    for (int i = 1; i <= n; i++)
        ans[i] -= mi;
    for (int i = 1; i <= n; i++) 
        cout << ans[i] << " ";
    return 0;   
}
posted @ 2023-01-05 12:21  came11ia  阅读(51)  评论(0编辑  收藏  举报