Codeforces Global Round 30 (Div. 1 + Div. 2)


A. Sequence Game

题意:一个数组\(a\),每次选择两个相邻的数,用它们之间的一个值替换它们两个。求最后能不能使得留下的数是\(x\)

如果\(\min(a) \leq x \leq \max(a)\)则可行。
最小值和最大值和别人操作时可以留下自己,然后让这两个值最后一起操作。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	int x;
	std::cin >> x;
	if (x <= std::ranges::max(a) && x >= std::ranges::min(a)) {
		std::cout << "YES\n";
	} else {
		std::cout << "NO\n";
	}
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

B. Even Modulo Pair

题意:给你一个严格递增的序列,求有没有两个数\(x, y\)满足\(y \mod x\)是偶数。其中\(y > x\)

如果至少有两个偶数,那么偶数模偶数是偶数。
否则考虑一堆奇数的情况,如果\(a_i\)\(a_{i-1}\)是奇数,设\(a_i = qa_{i-1} + r\),其中\(r\)是奇数,那么\(q\)是偶数,否则奇数乘奇数加奇数会是一个偶数。然后如果\(q = 0\)\(r = a_i - a_{i-1}\)\(r\)应该是偶数,所以这个情况不成立。那么也就是说\(a_i > 2a_{i-1}\)
那么当\(n\)很大时,必然有解,且直接双层循环暴力很快就能找到解。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::vector<int> a(n);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	int even = -1;
	for (int i = 0; i < n; ++ i) {
		if (a[i] % 2 == 0) {
			if (even != -1) {
				std::cout << even << " " << a[i] << "\n";
				return;
			} else {
				even = a[i];
				for (int j = 0; j < i; ++ j) {
					if (a[i] % a[j] % 2 == 0) {
						std::cout << a[j] << " " << a[i] << "\n";
						return;
					}
				}
			}
		} else {
			for (int j = i + 1; j < n; ++ j) {
				if (a[j] % a[i] % 2 == 0) {
					std::cout << a[i] << " " << a[j] << "\n";
					return;
				}
			}
		}
	}
	std::cout << -1 << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

C. Dungeon

题意:有\(n\)把剑,每把攻击力为\(a_i\)。有\(m\)个怪物,两个属性\(b_i, c_i\)。如果你用\(i\)这把剑攻击\(j\)这个怪物,需要满足\(a_i \geq b_j\)才可以。且当\(c_j > 0\)时,\(a_i\)会变成\(\max(a_i, c_j)\),否则第\(i\)把剑不能再用。每个怪物也只能杀一次。求最多杀几个。

考虑把\(c_i = 0\)的怪物留到最后杀。
我们希望尽可能提高剑的攻击力,那么可以把剑从小到大排序,怪物按\(b_i\)也从小到大排序,每次选一个大于等于\(b_i\)最小的剑出来,其它更小的剑可以留着杀\(c_i = 0\)的怪物。那么每次最小的剑可能变大,相当于用一个大剑换了一个小剑。最后把所有剑从小到大贪心攻击\(c_i = 0\)的怪物就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<int> a(n), b(m), c(m);
	for (int i = 0; i < n; ++ i) {
		std::cin >> a[i];
	}

	for (int i = 0; i < m; ++ i) {
		std::cin >> b[i];
	}

	std::vector<std::pair<int, int>> d(m);
	for (int i = 0; i < m; ++ i) {
		std::cin >> c[i];
		d[i] = {b[i], c[i]};
	}

	std::ranges::sort(d);

	std::multiset<int> s(a.begin(), a.end());
	int ans = 0;
	std::vector<int> e;
	std::multiset<int> s1;
	for (auto & [x, y] : d) {
		while (s.size() && *s.begin() < x) {
			s1.insert(*s.begin());
			s.erase(s.begin());
		}

		if (s.size()) {
			auto it = s.begin();
			int v = *it;
			if (y != 0) {
				++ ans;
				s.erase(it);
				v = std::max(v, y);
				s.insert(v);
			} else {
				e.push_back(x);
			}
		} else {
			break;
		}
	}

	s.insert(s1.begin(), s1.end());
	for (auto & x : e) {
		while (s.size() && *s.begin() < x) {
			s.erase(s.begin());
		}

		if (s.size()) {
			++ ans;
			s.erase(s.begin());
		} else {
			break;
		}
	}

	std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

D. Copy String

题意:两个字符串\(s, t\),你想要把\(s\)变成\(t\)。每一轮操作力,你可以让\(s'_i = s_i\)\(s_{i-1}\)。然后\(s = s'\)。求最小操作数和方案。如果最小操作数大于\(k_max\)输出\(-1\)

显然操作只能把前面的字符蔓延到后面。我们可以从后往前看,每次找\(s_j = t_i\)的最后的\(j\),这个\(j\)不能超过后面选择的位置。
具体说,我们记录一个\(last\),表示\(t_{i+1}\)选择的位置,那么我们需要移动\(i+1 - last\)步把\(s_last\)处的字符蔓延到\(t_{i+1}\)。这个过程肯定会覆盖\(t_i\)一次。所以\(t_i\)选择的位置必须小于等于\(last\)。如果没有这样的位置无解。
然后我们可以按照每个位置选择的位置,每次操作中把还没到位的往后移就行。操作数就是最远的两个位置。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

void solve() {
	int n, K;
	std::cin >> n >> K;
	std::string s, t;
	std::cin >> s >> t;
	if (s[0] != t[0]) {
		std::cout << -1 << "\n";
		return;
	}

    std::array<std::vector<int>, 26> pos;
    std::vector<int> p(n), d(n);
    int k = 0;
    for (int i = 0; i < n; ++ i) {
        pos[s[i] - 'a'].push_back(i);
    }

    int last = n;
    for (int i = n - 1; i >= 0; -- i) {
        auto it = std::ranges::upper_bound(pos[t[i] - 'a'], std::min(i, last));
        if (it == pos[t[i] - 'a'].begin()) {
            std::cout << -1 << "\n";
            return;
        }

        -- it;
        p[i] = *it;
        d[i] = i - p[i];
        last = std::min(last, p[i]);
        k = std::max(k, d[i]);
    }

    if (k > K) {
        std::cout << -1 << "\n";
        return;
    }

    std::cout << k << "\n";
    for (int i = 1; i <= k; ++ i) {
        std::string ns = s;
        for (int j = 1; j < n; ++ j) {
            if (d[j] >= i) {
                ns[j] = s[j - 1];
            }
        }

        std::cout << ns << "\n";
        s = ns;
    }
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}

E. Journey

题意:一个\(n\)个点\(m\)条边的无向图。每条边有边权。你从\(1\)出发,需要经过每条边恰好一次,代价为这条边的边权。你还有一个传送操作,对于点\(u, v\),从\(u\)传送到\(v\)的代价为其一条路径上编号最大的边的边权。路径不一定是简单路径,你可以经过一个点或一条边多次。最后你需要回到\(1\)点。求最小代价。

由于每条边必须经过一次,所以答案至少为所有边的边权和。我们需要考虑怎样使用传送操作,使得增加的代价最小。
要求经过每一条边并且最后回到起点,可以想到欧拉回路。在欧拉回路中,要求每个点的度数为偶数。现在有传送操作,意味着我们可以在奇数点之间进行传送,也就是我们需要把奇数点两两匹配。
考虑\(kruskal\)按编号从小到大枚举边,考虑一条边连接两个连通块,如果从这两个连通块匹配两个点的话,就需要经过这条边,那么可以把这条边看作一个新点,连接这两个连通块的根节点。这样做之后,叶子节点就恰好是原图里的\(n\)个点,其它非叶子节点都是一条边。并且每个点父节点的编号大于子节点的编号。那么对于这棵树的一个非叶子节点,将其两个子树的点两两匹配,可以得到的最小代价为这个点和它所有祖先节点的最小值。
那么在这棵树上\(dfs\),贪心匹配就行。

点击查看代码
#include <bits/stdc++.h>

using i64 = long long;

struct DSU {
    std::vector<int> fa, cnt;
    DSU();
    DSU(int n) {
        init(n);
    }

    void init(int n) {
        fa.assign(n + 1, 0);
        cnt.assign(n + 1, 1);
        std::ranges::iota(fa, 0);
    }

    int find(int x) {
        return x == fa[x] ? x : fa[x] = find(fa[x]);
    }

    bool merge(int x, int y) {
        int u = find(x), v = find(y);
        if (u == v) {
            return false;
        }

        fa[v] = u;
        cnt[u] += cnt[v];
        return true;
    }

    bool same(int u, int v) {
        return find(u) == find(v);
    }

    int size(int u) {
        return cnt[find(u)];
    }
};

void solve() {
	int n, m;
    std::cin >> n >> m;
    std::vector<std::tuple<int, int, int>> edges(m);
    i64 ans = 0;
    std::vector<int> deg(n);
    for (int i = 0; i < m; ++ i) {
        int u, v, w;
        std::cin >> u >> v >> w;
        -- u, -- v;
        edges[i] = {u, v, w};
        deg[u] ^= 1;
        deg[v] ^= 1;
        ans += w;
    }

    const int inf = 1e9;
    std::vector<std::vector<int>> adj(2 * n);
    std::vector<int> w1(2 * n, inf);
    DSU dsu(2 * n);
    int N = n;
    for (int i = 0; i < m; ++ i) {
        auto & [u, v, w] = edges[i];
        int x = dsu.find(u);
        int y = dsu.find(v);
        if (x == y) {
            w1[x] = std::min(w1[x], w);
            continue;
        }

        dsu.merge(x, y);
        dsu.merge(N, x);
        adj[N].push_back(x);
        adj[N].push_back(y);
        w1[N ++ ] = w;
    }

    int root = dsu.find(0);
    std::vector<int> cnt(2 * n);
    auto dfs = [&](auto && self, int u, int min) -> void {
        if (u < n) {
            cnt[u] = deg[u];
            return;
        }

        min = std::min(w1[u], min);
        int x = adj[u][0], y = adj[u][1];
        self(self, y, min);
        self(self, x, min);
        int t = std::min(cnt[x], cnt[y]);
        cnt[x] -= t;
        cnt[y] -= t;
        ans += (i64)min * t;
        cnt[u] = cnt[x] + cnt[y];
    };

    dfs(dfs, root, inf);

    std::cout << ans << "\n";
}

int main() {
	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
	int t = 1;
	std::cin >> t;
	while (t -- ) {
		solve();
	}
	return 0;
}
posted @ 2025-11-07 02:00  maburb  阅读(497)  评论(0)    收藏  举报