AtCoder Beginner Contest 396
A - Triple Four
点击查看代码
void solve() {
int n;
std::cin >> n;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i + 2 < n; ++ i) {
if (a[i] == a[i + 1] && a[i + 1] == a[i + 2]) {
std::cout << "Yes\n";
return;
}
}
std::cout << "No\n";
}
B - Card Pile
维护一个栈
点击查看代码
void solve() {
int q;
std::cin >> q;
std::stack<int> s;
for (int i = 0; i < 100; ++ i) {
s.push(0);
}
while (q -- ) {
int t, x;
std::cin >> t;
if (t == 1) {
std::cin >> x;
s.push(x);
} else {
std::cout << s.top() << "\n";
s.pop();
}
}
}
C - Buy Balls
题意:给你两个数组,每个数组选一些数,第一个数组选的数的个数要大于等于第二个数组的数。求选出数的最大值。
两个数组从大到小排序,然后维护第二个数组的前缀最大值,第一个数组一直取下去,每个前缀取最大值。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n), b(m);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
for (int i = 0; i < m; ++ i) {
std::cin >> b[i];
}
std::sort(a.begin(), a.end(), std::greater<int>());
std::sort(b.begin(), b.end(), std::greater<int>());
i64 ans = 0, sum = 0, pre = 0;
for (int i = 0; i < n; ++ i) {
sum += a[i];
if (i < m) {
pre = std::max(pre, pre + b[i]);
}
ans = std::max(ans, sum + pre);
}
std::cout << ans << "\n";
}
D - Minimum XOR Path
题意:求\(1\)到\(n\)的路径的最小异或值。
\(n\)很小,直接爆搜。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<std::pair<int, i64>>> adj(n);
for (int i = 0; i < m; ++ i) {
int u, v;
i64 w;
std::cin >> u >> v >> w;
-- u, -- v;
adj[u].push_back({v, w});
adj[v].push_back({u, w});
}
i64 ans = (1ll << 60) - 1;
std::vector<int> st(n);
auto dfs = [&](auto self, int u, i64 val) -> void {
if (u == n - 1) {
ans = std::min(ans, val);
}
for (auto & [v, w] : adj[u]) {
if (st[v]) {
continue;
}
st[v] = 1;
self(self, v, val ^ w);
st[v] = 0;
}
};
st[0] = 1;
dfs(dfs, 0, 0);
std::cout << ans << "\n";
}
E - Min of Restricted Sum
题意:有\(n\)个数,给出\(m\)条限制,\(a_{x_i} \oplus a_{y_i} = z_i\)。求所有合法序列里总和最小的。
对于每一对\(x_i, y_i\)可以用并查集把它们合并到一个集合,维护每个点到根的异或和就行。
那么我们把每个联通块拿出来,给根节点赋值,发现如果\(u\)到\(root\)的异或和的第\(i\)位如果是\(1\),那么\(root\)这一位取\(1\)就可以让\(u\)这一位为\(0\),否则\(root\)取\(0\)可以让\(u\)这一位为\(0\)。那么统计每一个联通块的点与根的异或和的每一位的\(1\)的个数,如果\(1\)的个数大于等于联通块大小的一半,就给\(root\)这一位赋值\(1\),否则赋值\(0\)。
点击查看代码
struct DSU {
std::vector<int> fa, cnt, val;
DSU(int _n) {
init(_n);
}
void init(int _n) {
fa.assign(_n, 0);
cnt.assign(_n, 1);
val.assign(_n, 0);
std::iota(fa.begin(), fa.end(), 0);
}
std::pair<int, int> find(int u) {
if (fa[u] != u) {
int old = fa[u];
fa[u] = find(fa[u]).first; // 路径压缩
val[u] ^= find(old).second; // 更新异或值
}
return {fa[u], val[u]};
}
bool merge(int x, int y, int z) {
auto [u, val_x] = find(x);
auto [v, val_y] = find(y);
if (u == v) {
if ((val_x ^ val_y) != z) {
return false;
}
return true;
}
fa[v] = u;
cnt[u] += cnt[v];
val[v] = val_x ^ val_y ^ z;
return true;
}
bool same(int x, int y) {
return find(x).first == find(y).first;
}
};
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::array<int, 3>> a(m);
for (int i = 0; i < m; ++ i) {
int x, y, z;
std::cin >> x >> y >> z;
-- x, -- y;
a[i] = {x, y, z};
}
DSU dsu(n);
for (auto & [x, y, z] : a) {
if (!dsu.merge(x, y, z)) {
std::cout << -1 << "\n";
return;
}
}
std::vector<std::vector<std::pair<int, int>>> g(n);
for (int i = 0; i < n; ++ i) {
auto [u, x] = dsu.find(i);
g[u].push_back({i, x});
}
std::vector<int> ans(n);
for (int i = 0; i < n; ++ i) {
auto & A = g[i];
int k = A.size();
std::array<int, 30> cnt{0};
for (auto & [x, v] : A) {
for (int j = 0; j < 30; ++ j) {
if (v >> j & 1) {
++ cnt[j];
}
}
}
int res = 0;
for (int j = 0; j < 30; ++ j) {
if (cnt[j] >= k - cnt[j]) {
res |= 1 << j;
}
}
for (auto & [x, v] : A) {
ans[x] = res ^ v;
}
}
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] << " \n"[i == n - 1];
}
}
F - Rotated Inversions
题意:给你一个数组,对于每个\(k \in [0, m - 1]\),使得\(a_i = (a_ i + k) \% m\)。求这\(k\)个情况的逆序对。
分析一下,对于\(i, j(i < j)\)来说,它们在\(k\)取什么值的情况会产生逆序对。
如果\(a_i > a_j\),则当\(k \in [0, m - a_i)\)或\(k \in [m - a_j, m)\)它们产生逆序对。
如果\(a_i < a_j\),则当\(k \in [m - a_j, m - a_i)\)它们产生逆序对。
那么我们可以枚举\(i, j\),然后对它们产生贡献的\(k\)的区间加一,这样就变成了区间加的问题,可以用差分维护。但这样依然是\(n^2\)的。
在差分的角度,我们来看\(j\)什么时候产生\(1\)的贡献。观察上面两种情况,发现对于每个\(i < j, a_i \neq a_j\)的位置,都在差分数组的\(m - a_j\)加\(1\),同时对于每个\(i > j, a_i \neq a_j\)的位置加\(-1\)。对于\(a_i > a_j\)的\([m - a_j, m)\)这个区间,实际我们不会差分到\(m\)这个地方,所以可以不考虑这个位置应该减去的\(1\)。但对于\([0, m - a_i)\)这个区间,我们在\(m - a_i\)处减了\(1\),那么\(0\)这个位置的\(1\)该如何记录?我们可以先把\(k=0\)的情况的逆序对求出来,那么对于应该给\(0\)这个位置加\(1\)的位置我们就不用管了。然后就可以直接前缀和。
点击查看代码
template <class T>
struct Fenwick {
int n;
std::vector<T> tr;
Fenwick(int _n) {
init(_n);
}
void init(int _n) {
n = _n;
tr.assign(_n + 1, T{});
}
void add(int x, const T &v) {
for (int i = x; i <= n; i += i & -i) {
tr[i] = tr[i] + v;
}
}
T query(int x) {
T res{};
for (int i = x; i; i -= i & -i) {
res = res + tr[i];
}
return res;
}
T sum(int l, int r) {
return query(r) - query(l - 1);
}
};
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<int> a(n);
for (int i = 0; i < n; ++ i) {
std::cin >> a[i];
}
Fenwick<int> tr(m + 1);
i64 sum = 0;
for (int i = 0; i < n; ++ i) {
sum += tr.sum(a[i] + 2, m + 1);
tr.add(a[i] + 1, 1);
}
std::vector<i64> ans(m + 1);
ans[0] = sum;
std::vector<int> cnt(m);
for (int i = 0; i < n; ++ i) {
ans[m - a[i]] += i - cnt[a[i]];
cnt[a[i]] += 1;
}
std::fill(cnt.begin(), cnt.end(), 0);
for (int i = n - 1; i >= 0; -- i) {
ans[m - a[i]] -= (n - 1 - i) - cnt[a[i]];
cnt[a[i]] += 1;
}
for (int i = 1; i < m; ++ i) {
ans[i] += ans[i - 1];
}
for (int i = 0; i < m; ++ i) {
std::cout << ans[i] << " \n";
}
}