C. Challenge NPC
考慮構造一個二分圖,左邊是\(1,3,5,7\)右側是\(2,4,6,8\)。最優解肯定是一邊全 1,一邊全 2。
如果\(1,2\)之間不連邊,這\(2\)就會被染色為 1,因此只要讓\(2,3\)連邊,\(3\)會被染色為\(2\),然後\(1,4\)連邊,\(4\)也會被染色為\(5\),這時只要讓\(2,5\)和\(4,5\)連邊,\(5\)就會被染色為\(3\)。可以以此類推下去,這樣左右的顏色都會依次是\(1,2,3,4,\dots\),因此只要讓左右兩側都有\(k+2\)個點就好了
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int k;
cin >> k;
int n = (k + 2) * 2, m = n * (n - 2) / 4, c = 2;
cout << n << " " << m << " " << c << "\n";
for (int i = 1; i <= n; i++)
cout << i % 2 + 1 << " ";
cout << "\n";
for (int i = 1, t; i <= n; i++) {
t = i + 2;
if (i & 1) t++;
else t--;
for (; t <= n; t += 2)
cout << i << " " << t << "\n";
}
return 0;
}
E. Team Arrangement
假設我們知道分組的方案後,我們把組按照大小排序,然後貪心的選擇\(r\)較小的即可。
然後考慮如果得到分組方案,因為\(n\)最大隻有\(60\),所以直接暴搜列舉出來就好了。
#include <bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using vi = vector<int>;
using pii = pair<int, int>;
const int inf = LLONG_MAX / 2;
i32 main() {
int n;
cin >> n;
vector<pii> a(n);
for (auto &[l, r]: a)
cin >> l >> r;
ranges::sort(a);
vi w(n + 1);
for (int i = 1; i <= n; i++) cin >> w[i];
int res = -inf;
vi p;
auto dfs = [&](auto &self, int m, int x) -> void {
if (m == 0) {
priority_queue<int, vi, greater<>> heap;
int i = 0, ok = 1, sum = 0;
for (auto pi: p) {
while (i < n and a[i].first <= pi)
heap.push(a[i++].second);
for (int j = 0; j < pi and ok; j++) {
if (not heap.empty() and heap.top() >= pi) heap.pop();
else ok = 0;
}
if (ok == 0) break;
sum += w[pi];
}
if (ok) res = max(res, sum);
return;
}
for (int y = x; y <= m; y++) {
p.push_back(y);
self(self, m - y, y);
p.pop_back();
}
};
dfs(dfs, n, 1);
if (res == -inf) cout << "impossible\n";
else cout << res << "\n";
return 0;
}
F. Stage: Agausscrab
簽到題
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
const int mod = 1e9 + 7;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int n;
cin >> n;
vector<string> s(n);
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> s[i] >> a[i];
}
string res;
for (int i = 0, r; i < n; i++) {
r = 1;
for (int j = 0; j < n and r <= s.size(); j++) {
if (a[j] > a[i]) r++;
}
while (not s[i].empty() and r > 0)
s[i].pop_back(), r--;
res += s[i];
}
res[0] = res[0] - 'a' + 'A';
cout << "Stage: " << res;
return 0;
}
J. Even or Odd Spanning Tree
首先,可以先求一個最小生成樹出來。最小生成樹一定是其中一個答案。
然後考慮另一個答案一定是最小生成樹換一條邊得到的。
下面簡單的證明一下。
假設我們必須要換兩條邊,我們換之前的邊是\(a,b\)換之後的是\(x,y\),且\(a < b < x < y\)
則換邊後的權值變化了\(x + y - a - b\)。
如果原始的邊是一奇一偶,新邊一定是同奇或同偶
如果原始的邊是同奇或同偶,新邊一定是一奇一偶
這樣的話,一定可以保證只有一條邊與原來的邊奇偶不同,因此只換一條邊也可滿足奇偶變化。
現在這樣的組合有\(a \rightarrow x , a \rightarrow y , b \rightarrow x , b \rightarrow y\)
對於\(a\rightarrow x\) 權值的變化是 \(x - a < x + y - a - b\),所以這種情況下換\(a \rightarrow x\) 更優
同理可以推出其他三種。
因此無論什麼情況下,均勻換一條邊更優的情況出現。
現在我們要考慮的就是選擇那兩條邊。
考慮到,樹上刪掉任意一條邊,均會對聯通性發生改變,所以,新邊一定和舊邊在一個環上。
因此我們可以列舉所有不在最小生成樹上的邊,然後查詢兩個端點在樹上路徑中奇偶性不同且最大邊一定是最優的。
查詢最大邊可以用 lca+樹上倍增實現,複雜度\(O(\log n)\),所以整體的複雜度就是\(O(m\log n)\)
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
#define int i64
using edge = array<int, 3>;
using pii = pair<int, int>;
using vi = vector<int>;
const int inf = LLONG_MAX / 2;
class dsu {
private:
vector<int> fa;
public:
dsu(int n = 1) {
fa = vector<int>(n + 1, -1), fa[0] = 0;
}
int getfa(int x) {
if (fa[x] < 0) return x;
return fa[x] = getfa(fa[x]);
}
void merge(int x, int y) {
x = getfa(x), y = getfa(y);
if (x == y) return;
if (fa[x] > fa[y]) swap(x, y);
fa[x] += fa[y], fa[y] = x;
}
bool same(int x, int y) {
x = getfa(x), y = getfa(y);
return (x == y);
}
};
void solve() {
int n, m;
cin >> n >> m;
vector<edge> e(m);
for (auto &[z, x, y]: e)
cin >> x >> y >> z;
ranges::sort(e);
vector<vector<pii>> mst(n + 1);
vector<edge> ne;
dsu d(n);
int sum = 0, cnt = 0;
for (auto &[w, x, y]: e) {
if (d.same(x, y)) ne.push_back({w, x, y});
else {
sum += w, d.merge(x, y), cnt++;
mst[x].emplace_back(y, w);
mst[y].emplace_back(x, w);
}
}
if (cnt != n - 1) {
cout << "-1 -1\n";
return;
}
e = move(ne);
int dn = 0, lg2N = log2(n);
vector<int> dfn(n + 1), dep(n + 1);
vector<vi> f(n + 1, vi(lg2N + 1));
vector<vi> fa(n + 1, vi(lg2N + 1));
vector edgeMax(2, vector(n + 1, vi(lg2N + 1)));
for (int t = 0; t < 2; t++) {
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= lg2N; j++) {
if (edgeMax[t][i][j] == 0) continue;
assert(edgeMax[t][i][j] % 2 == t);
}
}
}
auto dfs = [&](auto &&self, int x, int ff) -> void {
f[dfn[x] = ++dn][0] = ff;
for (auto [y, w]: mst[x]) {
if (y == ff) continue;
fa[y][0] = x, dep[y] = dep[x] + 1;
edgeMax[w & 1][y][0] = w;
self(self, y, x);
}
};
auto get = [&](int x, int y) -> int {
if (dfn[x] < dfn[y]) return x;
return y;
};
auto lca = [&](int u, int v) -> int {
if (u == v) return u;
u = dfn[u], v = dfn[v];
if (u > v) swap(u, v);
int d = log2(v - u++);
return get(f[u][d], f[v - (1 << d) + 1][d]);
};
dep[1] = 1;
dfs(dfs, 1, 0);
for (int i = 1; i <= lg2N; i++)
for (int j = 1; j + (1 << i) - 1 <= n; j++)
f[j][i] = get(f[j][i - 1], f[j + (1 << i - 1)][i - 1]);
for (int i = 1; i <= lg2N; i++)
for (int j = 1; j <= n; j++) {
fa[j][i] = fa[fa[j][i - 1]][i - 1];
for (int t = 0; t < 2; t++)
edgeMax[t][j][i] = max(edgeMax[t][j][i - 1], edgeMax[t][fa[j][i - 1]][i - 1]);
}
auto query = [&](int w, int x, int y) -> int {
int z = lca(x, y), res = -1;
for (int i = lg2N; i >= 0 and x != z; i--) {
if (dep[fa[x][i]] < dep[z]) continue;
res = max(res, edgeMax[w][x][i]);
x = fa[x][i];
}
for (int i = lg2N; i >= 0 and y != z; i--) {
if (dep[fa[y][i]] < dep[z]) continue;
res = max(res, edgeMax[w][y][i]);
y = fa[y][i];
}
return res;
};
int res = inf;
for (auto [w, x, y]: e) {
auto t = query((w % 2) ^ 1, x, y);
if (t <= 0) continue;
res = min(res, sum - t + w);
}
if (res == inf) res = -1;
if (sum & 1) cout << res << " " << sum << "\n";
else cout << sum << " " << res << "\n";
return;
}
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
M. Triangles
一個比較有意思的數學題。
首先要打表找規律,在不刪除線的情況下,三角形的數目是\(\frac{n(n + 2 )(2n + 1)}{8}\)
然後考慮刪掉橫線在哪些三角形裡面。
我們考慮頂點在上的情況。
我們可以列舉出底邊的左端點\(i\)和長度\(x\),列舉左端點\([1,b]\)範圍內。
然後右端點自然就是\(i+x\),那麼\(i+x\)要滿足\(b + 1 \le i+x \le a\)
在考慮頂點,頂點是在\(a - x\)行,那麼$ 0\le a-x \le a - 1$
因此聯立兩個方程,可得$ \max(1, b - i + 1 ) \le x \le \min(a , a-i + 1)$;
考慮頂點在下的情況,我們列舉右端點\(i\)長度\(x\),類比可得到\(\max ( 1 , i - b )\le x \le \min(n - a, i - 1)\)
滿足上述條件的\(x\)均成立,因此我們可以\(O(1)\)的計算出每個\(i\)的貢獻
#include<bits/stdc++.h>
using namespace std;
using i32 = int32_t;
using i64 = long long;
i32 main() {
ios::sync_with_stdio(false), cin.tie(nullptr);
i64 n, a, b;
cin >> n >> a >> b;
i64 sum = n * (n + 2) * (2 * n + 1) / 8;
for (i64 i = 1; i <= b; i++) {
i64 l = max(1ll, b - i + 1), r = min(a, a - i + 1);
if (l <= r) sum -= r - l + 1;
}
for (i64 i = b + 1; i <= a + 1; i++) {
i64 l = max(1ll, i - b), r = min(n - a, i - 1);
if (l <= r) sum -= r - l + 1;
}
cout << sum;
return 0;
}