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