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