2024 Xiangtan University Summer Camp-Div.2
A. 二度树上的染色游戏
因为题目保证了是二叉树,所以每次至多只需要选择一个子节点染成红色。所以可以贪心的选择红色权值小的子树即可。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ldb = long double;
const i32 inf = INT_MAX / 2;
const i64 INF = LLONG_MAX / 2;
#define int i64
using vi = vector<int>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vi a(n + 1);
int sum = 0;
for (int i = 1; i <= n; i++)
cin >> a[i], sum += a[i];
vector<vi> e(n + 1);
for (int i = 1, u, v; i < n; i++) {
cin >> u >> v;
e[u].push_back(v), e[v].push_back(u);
}
auto dfs = [&](auto &&self, int x, int fa) -> int {
int cnt = 0;
for (int y: e[x])
cnt += y != fa;
if (cnt < 2) return a[x];
int ans = inf;
for (int y: e[x]) {
if (y == fa) continue;
ans = min(ans, self(self, y, x));
}
return ans + a[x];
};
int red = dfs(dfs, 1, -1);
cout << sum - red - red;
return 0;
}
B. 小文的排列
我们可以把序列分层若干个子段,这样的话,除了开头的子段和结尾的子段,剩下的字段必须都是完整的\(m\)的排列,并且开头和结尾必须是\(m\)排列的子段。
因此我们可以用双指针扫描出所有的合法子段,然后做一个简单的dp 就好了。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ldb = long double;
const i32 inf = INT_MAX / 2;
const i64 INF = LLONG_MAX / 2;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, m;
cin >> n >> m;
vi a(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
if (ranges::max(a) > m) {
cout << "NO\n";
return 0;
}
vector<vi> e(n + 1);
set<int> vis;
for (int l = 1, r = 0; l <= n; l++) {
while (r + 1 <= n and vis.count(a[r + 1]) == 0) {
r++, vis.insert(a[r]);
if (l == 1) e[r].push_back(1);
}
if (r == n or vis.size() == m)
e[r].push_back(l);
vis.erase(a[l]);
}
vi f(n + 1);
f[0] = 1;
for (int r = 1; r <= n; r++)
for (int l: e[r])
if (f[l - 1]) {
f[r] = 1;
break;
}
if (f[n]) cout << "YES";
else cout << "NO";
return 0;
}
C. gcd hard version
区间最大公约数可以用ST表求,区间和可以用前缀和求。
对于当前的区间\([l,r]\),如果\(r\)变大,最大公约数是不增的,区间和是不减的,因此如果\([l,r]\)满足,\([l,r+1]\)一定满足。
所以我们可以枚举左端点,然后直接二分出右端点。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ldb = long double;
const i32 inf = INT_MAX / 2;
const i64 INF = LLONG_MAX / 2;
#define int i64
using vi = vector<int>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
int lg2N = log2(n);
vi lg2(n + 1);
vector f(n + 1, vi(lg2N + 1));
lg2[0] = -1;
for (int i = 1; i <= n; i++) {
cin >> f[i][0];
lg2[i] = lg2[i / 2] + 1;
}
for (int j = 1; j <= lg2N; j++)
for (int i = 1; i + (1 << j) - 1 <= n; i++)
f[i][j] = gcd(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
auto query = [=](int l, int r) {
assert(l <= r);
int s = lg2[r - l + 1];
return gcd(f[l][s], f[r - (1 << s) + 1][s]);
};
vi pre(n + 1);
for (int i = 1; i <= n; i++) {
cin >> pre[i];
pre[i] += pre[i - 1];
}
int res = 0;
for (int i = 1; i <= n; i++) {
int l = i, r = n, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (query(i, mid) <= pre[mid] - pre[i - 1]) ans = mid, r = mid - 1;
else l = mid + 1;
}
if (ans == -1) continue;
res += n - ans + 1;
}
cout << res;
return 0;
}
D. 战至终章
挑战的顺序一定是一种拓扑序。有多个可以挑战的情况下挑战\(a\)最小的一定最优。因此我们在求拓扑序的时候使用优先队列即可。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
using ldb = long double;
const i32 inf = INT_MAX / 2;
const i64 INF = LLONG_MAX / 2;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n, p;
cin >> n >> p;
vi a(n + 1), b(n + 1), deg(n + 1);
vector<vi> e(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> b[i];
priority_queue<pii> heap;
for (int i = 1; i <= n; i++) {
cin >> deg[i];
if (deg[i] == 0) heap.emplace(-a[i], i);
for (int j = 0, x; j < deg[i]; j++)
cin >> x, e[x].push_back(i);
}
vi res;
while (not heap.empty()) {
auto [_, x] = heap.top();
heap.pop();
if (a[x] > p) continue;
res.push_back(x), p += b[x];
for (auto y: e[x])
if (--deg[y] == 0) {
heap.emplace(-a[y], y);
}
}
ranges::sort(res);
cout << res.size() << "\n";
for (auto i: res) cout << i << " ";
return 0;
}