The 2018 ICPC Asia Qingdao Regional Programming Contest (The 1st Universal Cup, Stage 9: Qingdao)


C. Flippy Sequence

题意:给你两个长度相同的\(01\)\(s, t\),你必须操作两次使得他们相等。每次操作选择一个区间,使得\(s\)在这个区间的位置都取反。求有多少种操作方案。

\(s_i = t_i\)的地方为\(0\),否则为\(1\),发现最前的一段\(1\)后最后的一段\(1\)的区间之间不能有\(1\),这样不可能使得它们相等。那么只可能是\(0, 10, 01, 101\)这四种。分类讨论就行。

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

using i64 = long long;

void solve() {
	int n;
	std::cin >> n;
	std::string s, t;
	std::cin >> s >> t;
	int l1 = -1, r1 = -1;
	for (int i = 0; i < n; ++ i) {
		if (s[i] != t[i]) {
			int j = i;
			while (j + 1 < n && s[j + 1] != t[j + 1]) {
				++ j;
			}

			l1 = i, r1 = j;
			break;
		}
	}

	if (l1 == -1) {
		std::cout << (i64)n * (n + 1) / 2 << "\n";
		return;
	}

	int l2 = n, r2 = n;
	for (int i = n - 1; i >= 0; -- i) {
		if (s[i] != t[i]) {
			int j = i;
			while (j - 1 >= 0 && s[j - 1] != t[j - 1]) {
				-- j;
			}

			l2 = j, r2 = i;
			break;
		}
	}

	if (l1 == l2 && r1 == r2) {
		std::cout << (r1 - l1) * 2 + (n - 1 - r1 + l1) * 2 << "\n";
		return;
	}

	for (int i = r1 + 1; i < l2; ++ i) {
		if (s[i] != t[i]) {
			std::cout << 0 << "\n";
			return;
		}
	}

	int len1 = r1 - l1 + 1, len2 = l2 - r1 - 1, len3 = r2 - l2 + 1;
	std::cout << 6 << "\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. Magic Multiplication

题意:两个数字串做运算,其值为一个字符串,分别为\(a_1b_1, a_1b_2,...,a_1b_m,a_2b_1,...,a_2b_m,...,a_nb_m\)依次连接的字符串。现在给你这个字符串和\(a,b\)的长度。求合法的一对\(a,b\)

如果确定了\(a_1\),那么可以确定整个\(b\)。枚举\(a_1\)检查即可。

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

using i64 = long long;

int f[10][100];

void init() {
	for (int i = 1; i <= 9; ++ i) {
		for (int j = 1; j <= 9; ++ j) {
			f[i][i * j] = j;
		}
	}
}

void solve() {
	int n, m;
	std::string s;
	std::cin >> n >> m;
	std::cin >> s;
	std::vector<int> a(n), b(m);

	auto get = [&](int x, int & j) -> int {
		if (j >= s.size()) {
			return -1;
		}

		if (s[j] == '0') {
			++ j;
			return 0;
		}

		int t = s[j] - '0';
		if (f[x][t]) {
			++ j;
			return f[x][t];
		}

		if (j + 1 >= s.size()) {
			return -1;
		}

		t = t * 10 + s[j + 1] - '0';
		if (f[x][t]) {
			j += 2;
			return f[x][t];
		}

		return -1;
	};

	auto check = [&](int x) -> bool {
		a[0] = x;
		int p = 0;
		for (int i = 0; i < m; ++ i) {
			b[i] = get(x, p);
			if (b[i] == -1) {
				return false;
			}
		}

		if (b[0] == 0) {
			return false;
		}

		for (int i = 1; i < n; ++ i) {
			a[i] = get(b[0], p);
			if (a[i] == -1) {
				return false;
			}

			for (int j = 1; j < m; ++ j) {
				int x = a[i] * b[j];
				if (x < 10) {
					if (s[p] - '0' != x) {
						return false;
					}
					++ p;
				} else {
					if (p + 1 >= s.size() || ((s[p] - '0') * 10 + s[p + 1] - '0') != x) {
						return false;
					}
					p += 2;
				}
			}
		}

		return p == s.size();
	};

	for (int i = 1; i <= 9; ++ i) {
		if (check(i)) {
			for (int j = 0; j < n; ++ j) {
				std::cout << a[j];
			}
			
			std::cout << " ";

			for (int j = 0; j < m; ++ j) {
				std::cout << b[j];
			}
			std::cout << "\n";
			return;
		}
	}
	std::cout << "Impossible\n";
}

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

E. Plants vs. Zombies

题意:一个数组最初为空,你在第\(0\)点,如果你走到了第\(i\)点,这个位置就会增加\(a_i\)。你可以走\(m\)步,每次往左或往右,求最大的最小值。

最大最小,考虑二分。
如果我们想让所有数至少为\(x\),那么可以计算出来每个数至少被经过几次。最优策略是两个两个跳着走,于是模拟就行。

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

using i64 = long long;

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

	auto check = [&](i64 x) -> bool {
		std::vector<i64> b(n);
		for (int i = 0; i < n; ++ i) {
			b[i] = (x + a[i] - 1) / a[i];
		}

		b.push_back(0);
		i64 cnt = 0;
		for (int i = 0; i < n; ++ i) {
			if (b[i] == 0) {
				++ cnt;
				continue;	
			}
			cnt += b[i] * 2 - 1;
			if (cnt > m) {
				return false;
			}
			b[i + 1] = std::max(0ll, b[i + 1] - (b[i] - 1));
		}		

		return true;
	};

	i64 l = 0, r = 1e17;
	while (l < r) {
		i64 mid = l + r + 1 >> 1ll;
		if (check(mid)) {
			l = mid;
		} else {
			r = mid - 1;
		}
	}

	std::cout << l << "\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;
}

F. Tournament

题意:\(n\)个人进行\(k\)场比赛,每次两两比赛,要求如果某一场\(a\)\(b\)比,\(c\)\(d\)比,则如果\(a\)\(c\)比,\(b\)必须和\(d\)比。

打表题。\(k \leq lowbit(n) - 1\)才有解。具体看代码。

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

using i64 = long long;

void solve() {
	int n, k;
	std::cin >> n >> k;
	if (k >= (n & -n)) {
		std::cout << "Impossible\n";
		return;
	}

	for (int i = 1; i <= k; ++ i) {
		for (int j = 0; j < n; ++ j) {
			std::cout << (i ^ j) + 1 << " \n"[j == n - 1];
		}
	}
}

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

I. Soldier Game

题意:\(n\)个数,你要给他们分组,每组要么是单独一个人,要么是相邻的两个人。每个人只能在一个组里。一个组的值为两个人的和。求极差最小。

\(f[l][r][0][0]\)\(a_l\)的组都在\([l, r]\)内,\(a_r\)的分组都在\([l, r]\)内的最小最大值。
\(f[l][r][0][1]\)\(a_r, a_{r+1}\)一组。
\(f[l][r][1][0]\)\(a_{l-1}, a_l\)一组。
\(f[l][r][1][1]\)\(a_{l-1}, a_l\)一组,\(a_{r}, a_{r+1}\)一组。

那么可以枚举一个\(k\),得到\(f[l][r][i][j] = \min_{x=0}^{1}(f[l][k][i][x], f[k][r][x][j])\)。只不过这样时间复杂度无法通过。发现其实\(k\)的取值无所谓,我们可以直接取\(k = \lfloor \frac{l+r}{2} \rfloor\),那么可以用线段树来维护。那么\(u\)代表一个线段,\(f[u][i][j] = \min_{k=0}^{1}(f[u][i][j], \max(f[u << 1][i][k], f[u << 1 | 1][k][j])\)\(l==r\)时,初始为\(f[u][0][0] = a[l], f[u][0][1] = l + 1 <= n ? a[l] + a[l + 1] : inf, f[u][1][0] = l > 1 ? -inf : inf; f[u][1][1] = inf;\)
其中\(-inf\)区间合并是相当于让\(max\)取另一个区间,\(inf\)代表这个情况非法,\(max\)会取到它,然后和答案取\(min\)答案不变。
那么现在我们可以把所有长度为\(1\)的组和长度为\(2\)的组取出来按值排序。从小到大枚举最小值,\(ans = \min(ans, f[1][0][0] - w\)\(w\)为当前最小值。然后把这条线段设置为正无穷。相当于把它删除,也就是这个组不允许出现。

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

using i64 = long long;

const i64 inf = 1e18;

struct Info {
	std::array<std::array<i64, 2>, 2> f;
	Info() {
		std::ranges::fill(f[0], 0);
		std::ranges::fill(f[1], 0);
	}
};

const int N = 1e5 + 5;

i64 a[N];

struct SegmentTree {
	struct Node {
		int l, r;
		Info info;
	};

	int n;
	std::vector<Node> tr;
	SegmentTree(){}
	SegmentTree(int _n) : n(_n) {
		tr.assign(n << 2, {});
		build(0, n - 1);
	}

	void pushup(int u) {
		auto & cur = tr[u].info.f;
		auto & l = tr[u << 1].info.f;
		auto & r = tr[u << 1 | 1].info.f;
		for (int i = 0; i < 2; ++ i) {
			for (int j = 0; j < 2; ++ j) {
				cur[i][j] = inf;
				for (int k = 0; k < 2; ++ k) {
					cur[i][j] = std::min(cur[i][j], std::max(l[i][k], r[k][j]));
				}
			}
		}
	}

	void build(int l, int r, int u = 1) {
		tr[u] = {l, r, {}};
		if (l == r) {
			tr[u].info.f[0][0] = a[l];
			tr[u].info.f[0][1] = l + 1 < n ? a[l] + a[l + 1] : inf;
			tr[u].info.f[1][0] = l > 0 ? -inf : inf;
			tr[u].info.f[1][1] = inf;
			return;
		} 
		
		int mid = l + r >> 1;
		build(l, mid, u << 1);
		build(mid + 1, r, u << 1 | 1);
		pushup(u);
	}

	void modify(int p, int len) {
		int u = 1;
		while (tr[u].l != tr[u].r) {
			int mid = tr[u].l + tr[u].r >> 1;
			if (p <= mid) {
				u = u << 1;
			} else {
				u = u << 1 | 1;
			}
		}

		if (len == 1) {
			tr[u].info.f[0][0] = inf;
		} else {
			tr[u].info.f[0][1] = inf;
		}

		u >>= 1;
		while (u) {
			pushup(u);
			u >>= 1;
		}
	}

	i64 ans() {
		return tr[1].info.f[0][0];
	}
};

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

	SegmentTree tr(n);
	std::vector<std::tuple<i64, int, int>> b;
	for (int i = 0; i < n; ++ i) {
		b.emplace_back(a[i], i, 1);
		if (i + 1 < n) {
			b.emplace_back(a[i] + a[i + 1], i, 2);
		}
	}

	std::ranges::sort(b);
	i64 ans = inf;
	for (auto & [w, p, len] : b) {
		ans = std::min(ans, tr.ans() - w);
		tr.modify(p, len);
	}

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

J. Books

题意:有\(n\)个数,第\(i\)个价值\(a_i\),一个人拿走了其中\(m\)个数,他是从左往右依次能拿就拿。你需要求他最多有多少钱。

我们来看\(i, j\),其中\(i < j\),如果\(a_i < a_j\),那么如果能拿\(a_j\),意味着\(a_i\)也被拿了。如果\(a_i > a_j\),那么我们希望拿\(a_i\),因为我们要让钱最多。那么就是从前往后拿。
但要注意有\(0\)的情况,\(0\)是必拿的,我们\(0\)的个数大于\(m\)无解。否则我们去除\(0\)后,剩下拿前面的,然后后面的都不能拿,那就让剩下的钱是后面的最小值减一。

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

using i64 = long long;

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


	int cnt = std::ranges::count(a, 0);
	if (cnt > m) {
		std::cout << "Impossible\n";
		return;
	}

	if (m == n) {
		std::cout << "Richman\n";
		return;
	}

	i64 sum = 0;
	for (int i = 0; i < n; ++ i) {
		if (a[i] == 0) {
			continue;
		}
		if (cnt == m) {
			int min = 2e9;
			for (int j = i; j < n; ++ j) {
				if (a[j] != 0) {
					min = std::min(min, a[j]);
				}
			}
			sum += min - 1;
			break;
		} else {
			cnt += 1;
			sum += a[i];
		}
	}


	std::cout << sum << "\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;
}

M. Function and Function

题意:一个数的值定义为它的“圆圈”的数量,例如\(8\)有两个圈。\(f(x)\)\(x\)所有位数上的圆圈的个数。\(g^{k}(x) = g^{k-1}(f(x)), g^0(x) = x\)。求\(g^k(x)\)

容易发现,极少运算后值变为\(0\)\(1\),而\(0\)会变成\(1\)\(1\)会变成\(0\)。直接模拟就行。

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

using i64 = long long;


void solve() {
	int x, k;
	std::cin >> x >> k;
	std::map<int, int> mp;
	mp[0] = 1;
	mp[4] = 1;
	mp[6] = 1;
	mp[8] = 2;
	mp[9] = 1;
	for (int i = 0; i < k; ++ i) {
		if (x == 0 || x == 1) {
			x ^= k - i & 1;
			break;
		} 
		int nx = 0;	
		do {
			nx += mp[x % 10];
			x /= 10;
		} while (x);
		x = nx;
	}

	std::cout << x << "\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-06-04 22:46  maburb  阅读(17)  评论(0)    收藏  举报