比赛链接
官方题解
Problem A. Digits Sum
令 表示 的数位和,不难发现 。
因此,当且仅当 ,答案为 ,否则答案为 。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, ans;
int main() {
read(n);
while (n != 0) {
ans += n % 10;
n /= 10;
}
if (ans == 1) ans += 9;
writeln(ans);
return 0;
}
Problem B. RGB Coloring
考虑枚举非负整数 ,使得 。
不难发现可以将
的贡献分开考虑,因此,
的贡献为
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
const int P = 998244353;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int fac[MAXN], inv[MAXN];
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
int binom(int x, int y) {
if (y > x) return 0;
else return 1ll * fac[x] * inv[y] % P * inv[x - y] % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = 1ll * fac[i - 1] * i % P;
inv[n] = power(fac[n], P - 2);
for (int i = n - 1; i >= 0; i--)
inv[i] = inv[i + 1] * (i + 1ll) % P;
}
int n, ans; ll a, b, k;
int main() {
read(n), init(n);
read(a), read(b), read(k);
for (int i = 0; i <= n; i++) {
ll lft = k - a * i;
if (lft >= 0 && lft % b == 0 && lft / b <= n) update(ans, 1ll * binom(n, i) * binom(n, lft / b) % P);
}
writeln(ans);
return 0;
}
Problem C. Interval Game
不难发现,行走者的策略是唯一的。
考虑选择者的策略,首先,选择者不会考虑选择区间 让行走者连续两次走向同一个方向,因为只选择区间 能够得到同样的效果,因此,选择者一定会让行走者往返行走。
可以发现,贪心地选择让行走者走得最远的区间是最优的。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int delta = 1e5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
bool used[MAXN];
int n, l[MAXN], r[MAXN];
vector <int> hl[MAXN], hr[MAXN];
ll solve(int now, bool flg) {
for (int i = 1; i <= n; i++) {
used[i] = false;
hl[l[i]].push_back(i);
hr[r[i]].push_back(i);
}
ll ans = 0;
int p = 0, q = 2e5;
while (true) {
if (flg) {
while (q > now && (hl[q].empty() || used[hl[q].back()])) {
while (q > now && hl[q].empty()) q--;
while (q > now && !hl[q].empty() && used[hl[q].back()]) hl[q].pop_back();
}
if (q == now) break;
ans += q - now, now = q;
used[hl[q].back()] = true;
} else {
while (p < now && (hr[p].empty() || used[hr[p].back()])) {
while (p < now && hr[p].empty()) p++;
while (p < now && !hr[p].empty() && used[hr[p].back()]) hr[p].pop_back();
}
if (p == now) break;
ans += now - p, now = p;
used[hr[p].back()] = true;
}
flg ^= true;
}
return ans + abs(now - delta);
}
int main() {
read(n);
for (int i = 1; i <= n; i++) {
read(l[i]), read(r[i]);
l[i] += delta;
r[i] += delta;
}
writeln(max(solve(delta, false), solve(delta, true)));
return 0;
}
Problem D. Choosing Points
考虑对于给定点集 和输入 ,计算一个大小不小于 的独立集 。
对于 均为奇数的情况,可以直接对点集二分图染色。
对于 存在一个奇数的情况,不妨设为 ,可以对点集二分图染色,对得到的两个新点集 重新计算 ,取较大者。
对于 存在一个模 余 的数的情况,不妨设为 ,则 必定是两个奇数的平方和,同样可以对点集进行条纹染色,对得到的两个新点集 重新计算 ,取较大者。
否则,即 均为 的倍数,可以将点集分为四份,分别计算 ,合并得到的点集即可。
时间复杂度 。
实际上,通过上述过程,我们也可以发现对于单独的 ,得到的图是一张二分图,因此,对 分别二分图染色,取四份中较大的一份即可。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
vector <pair <int, int>> work(vector <pair <int, int>> input, int a) {
if (input.size() <= 1) return input;
vector <pair <int, int>> ans;
if (a % 2) {
int cnt[2] = {0, 0};
for (auto x : input) cnt[(x.first + x.second) & 1]++;
int home = cnt[0] > cnt[1] ? 0 : 1;
for (auto x : input) if ((x.first + x.second) % 2 == home) ans.push_back(x);
return ans;
}
if (a % 4) {
int cnt[2] = {0, 0};
for (auto x : input) cnt[x.first & 1]++;
int home = cnt[0] > cnt[1] ? 0 : 1;
for (auto x : input) if (x.first % 2 == home) ans.push_back(x);
return ans;
}
vector <pair <int, int>> nxt, tmp;
nxt.clear(); for (auto x : input) if (x.first % 2 == 0 && x.second % 2 == 0) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4); for (auto x : tmp) ans.emplace_back(x.first * 2, x.second * 2);
nxt.clear(); for (auto x : input) if (x.first % 2 == 0 && x.second % 2 == 1) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4); for (auto x : tmp) ans.emplace_back(x.first * 2, x.second * 2 + 1);
nxt.clear(); for (auto x : input) if (x.first % 2 == 1 && x.second % 2 == 0) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4); for (auto x : tmp) ans.emplace_back(x.first * 2 + 1, x.second * 2);
nxt.clear(); for (auto x : input) if (x.first % 2 == 1 && x.second % 2 == 1) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4); for (auto x : tmp) ans.emplace_back(x.first * 2 + 1, x.second * 2 + 1);
return ans;
}
vector <pair <int, int>> work(vector <pair <int, int>> input, int a, int b) {
if (input.size() <= 1) return input;
vector <pair <int, int>> ans;
if (a % 2 && b % 2) {
int cnt[2] = {0, 0};
for (auto x : input) cnt[(x.first + x.second) & 1]++;
int home = cnt[0] > cnt[1] ? 0 : 1;
for (auto x : input) if ((x.first + x.second) % 2 == home) ans.push_back(x);
return ans;
}
if (a % 2) swap(a, b);
if (b % 2) {
vector <pair <int, int>> nxt1, nxt2;
for (auto x : input) {
if ((x.first + x.second) & 1) nxt1.push_back(x);
else nxt2.push_back(x);
}
vector <pair <int, int>> tmp1 = work(nxt1, a);
vector <pair <int, int>> tmp2 = work(nxt2, a);
if (tmp1.size() > tmp2.size()) return tmp1;
else return tmp2;
}
if (a % 4) swap(a, b);
if (b % 4) {
vector <pair <int, int>> nxt1, nxt2;
for (auto x : input) {
if (x.first & 1) nxt1.push_back(x);
else nxt2.push_back(x);
}
vector <pair <int, int>> tmp1 = work(nxt1, a);
vector <pair <int, int>> tmp2 = work(nxt2, a);
if (tmp1.size() > tmp2.size()) return tmp1;
else return tmp2;
}
vector <pair <int, int>> nxt, tmp;
nxt.clear(); for (auto x : input) if (x.first % 2 == 0 && x.second % 2 == 0) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4, b / 4); for (auto x : tmp) ans.emplace_back(x.first * 2, x.second * 2);
nxt.clear(); for (auto x : input) if (x.first % 2 == 0 && x.second % 2 == 1) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4, b / 4); for (auto x : tmp) ans.emplace_back(x.first * 2, x.second * 2 + 1);
nxt.clear(); for (auto x : input) if (x.first % 2 == 1 && x.second % 2 == 0) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4, b / 4); for (auto x : tmp) ans.emplace_back(x.first * 2 + 1, x.second * 2);
nxt.clear(); for (auto x : input) if (x.first % 2 == 1 && x.second % 2 == 1) nxt.emplace_back(x.first / 2, x.second / 2);
tmp = work(nxt, a / 4, b / 4); for (auto x : tmp) ans.emplace_back(x.first * 2 + 1, x.second * 2 + 1);
return ans;
}
int main() {
int n, a, b; read(n), read(a), read(b);
vector <pair <int, int>> input;
for (int i = 0; i < n * 2; i++)
for (int j = 0; j < n * 2; j++)
input.emplace_back(i, j);
vector <pair <int, int>> res = work(input, a, b); int cnt = 0;
for (auto x : res) if (++cnt <= n * n) printf("%d %d\n", x.first, x.second);
return 0;
}
Problem E. Walking on a Tree
显然,记 表示第 条边被覆盖的次数,显然答案存在上界 。
考虑构造方案取到该上界,对于叶子节点
,令其父边被覆盖次数为
。
若
,显然,可以删去节点
。
考虑
的情况,任取两条覆盖该点的路径
。
则若要使得
被覆盖两次,
的方向必须不同。
考虑
的交
,
上的边都被覆盖了两次,在后续过程中都不需要继续考虑。
考虑
外的部分,可以发现
等价于
,而
等价于
,因此可以加入
代替上述两条路径参与后续计算。
由此,我们构造了一种方案。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5e3 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
vector <int> a[MAXN];
int depth[MAXN], father[MAXN];
int n, m, tot, ans, x[MAXN], y[MAXN];
bool used[MAXN], ok[MAXN], res[MAXN];
int nowx[MAXN], nowy[MAXN], f[MAXN], g[MAXN];
void dfs(int pos, int fa) {
depth[pos] = depth[fa] + 1;
father[pos] = fa;
for (auto x : a[pos])
if (x != fa) dfs(x, pos);
}
void mark(int a, int b, int c, int d) {
static int cnt[MAXN];
memset(cnt, 0, sizeof(cnt));
while (a != b) {
if (depth[a] < depth[b]) swap(a, b);
cnt[a]++, a = father[a];
}
while (c != d) {
if (depth[c] < depth[d]) swap(c, d);
cnt[c]++, c = father[c];
}
for (int i = 1; i <= n; i++)
ok[i] |= cnt[i] >= 2;
}
void work(int pos, int fa) {
for (auto x : a[pos])
if (x != fa) work(x, pos);
if (pos != 1) {
int cnt = 0;
for (int i = 1; i <= tot; i++)
if (!used[i]) cnt += (nowx[i] == pos) ^ (nowy[i] == pos);
ans += min(max(cnt, ok[pos] * 2), 2);
if (cnt <= 1 || ok[pos]) {
for (int i = 1; i <= tot; i++)
if (!used[i]) {
if (nowx[i] == pos) nowx[i] = fa;
if (nowy[i] == pos) nowy[i] = fa;
}
return;
}
vector <int> points;
for (int i = 1; i <= tot; i++)
if (!used[i] && ((nowx[i] == pos) ^ (nowy[i] == pos))) {
points.push_back(i);
}
int a = points[0], b = points[1];
mark(nowx[a], nowy[a], nowx[b], nowy[b]);
used[a] = used[b] = true;
if (nowx[a] == pos && nowx[b] == pos) {
tot++, f[tot] = -a, g[tot] = -b;
x[tot] = y[a], nowx[tot] = nowy[a];
y[tot] = y[b], nowy[tot] = nowy[b];
}
if (nowy[a] == pos && nowx[b] == pos) {
tot++, f[tot] = a, g[tot] = -b;
x[tot] = x[a], nowx[tot] = nowx[a];
y[tot] = y[b], nowy[tot] = nowy[b];
}
if (nowx[a] == pos && nowy[b] == pos) {
tot++, f[tot] = -a, g[tot] = b;
x[tot] = y[a], nowx[tot] = nowy[a];
y[tot] = x[b], nowy[tot] = nowx[b];
}
if (nowy[a] == pos && nowy[b] == pos) {
tot++, f[tot] = a, g[tot] = b;
x[tot] = x[a], nowx[tot] = nowx[a];
y[tot] = x[b], nowy[tot] = nowx[b];
}
for (int i = 1; i <= tot; i++)
if (!used[i]) {
if (nowx[i] == pos) nowx[i] = fa;
if (nowy[i] == pos) nowy[i] = fa;
}
}
}
int main() {
read(n), read(m), tot = m;
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
for (int i = 1; i <= m; i++) {
read(x[i]), read(y[i]);
nowx[i] = x[i];
nowy[i] = y[i];
}
dfs(1, 0);
work(1, 0);
writeln(ans);
for (int i = tot; i >= 1; i--) {
if (!used[i]) res[i] = true;
if (i > m) {
int a = abs(f[i]), b = abs(g[i]);
res[a] = res[i] ^ (x[i] != x[a]);
res[b] = res[i] ^ (y[i] != y[b]);
}
}
for (int i = 1; i <= m; i++)
if (res[i]) printf("%d %d\n", x[i], y[i]);
else printf("%d %d\n", y[i], x[i]);
return 0;
}
Problem F. Addition and Andition
令 表示两个高精度数从最低位开始的第 位。
定义一次对
的进位操作如下:
首先,令
。
若
或
,将其变为
,并对下一位
,令
,否则,结束进位操作。
定义一次加法操作如下:
从大到小对于每一个
的
,进行进位操作。
题目要求我们进行 次加法操作。
考虑如下过程,从大到小对于每一个
的
,进行
次如下操作
:
找到最小的
,使得
,对
进行进位操作,若不存在这样的
,则停止操作。
这是因为在同一次加法操作中,先进行的进位操作造成的增量将会大于之后进行的所有进位操作造成的增量之和,并且,对 进行进位操作后,有 ,因此,交换循环的顺序是可行的。
考虑用形式化的语言描述
:
令
表示当前进位,
表示剩余操作次数,初始时,令
,
。
对于
进行如下操作
:
若
, 令
,结束操作
若
, 令
,结束操作
若
, 令
, 若此时
, 令
,结束操作
若
, 令
若
, 令
若
, 令
, 若此时
, 令
,结束操作
可以类似地处理
的情况。
注意到证明循环可以交换的过程实际上证明了我们不会在 中遇到 的情况。
观察上述过程,处理大段 是可以做到较为快速的,因此我们可以对 分段,整段处理 的情况,具体实现时可以用一个栈来维护 的位置。
除了对于 的操作以外,剩余操作都将减少 的个数,因此该算法的时间复杂度是线性的。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e6 + 5;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
char s[MAXN];
int n, m, k, a[MAXN], b[MAXN];
int l, resa[MAXN], resb[MAXN];
stack <pair <int, int>> stk;
int main() {
read(n), read(m), read(k);
scanf("\n%s", s + 1);
for (int i = 1, j = n; i <= n; i++, j--)
a[j] = s[i] - '0';
scanf("\n%s", s + 1);
for (int i = 1, j = m; i <= m; i++, j--)
b[j] = s[i] - '0';
for (int i = max(n, m); i >= 1; i--) {
if (a[i] + b[i] == 1) stk.emplace(i, a[i] * 2 + b[i]);
if (a[i] + b[i] <= 1) continue;
int lft = k, pos = i, x = 3;
stack <pair <int, int>> add;
while (!stk.empty()) {
pair <int, int> tmp = stk.top();
if (pos == tmp.first) {
stk.pop();
if ((x ^ tmp.second) == 3) {
x = 3;
if (lft) lft--;
else break;
} else if (x == 3) {
x &= tmp.second;
add.emplace(tmp.first, tmp.second ^ 3);
}
pos += 1;
} else if (x == 3) {
if (tmp.first - pos > lft) break;
lft -= tmp.first - pos;
pos = tmp.first;
} else break;
}
if (x == 3) resa[pos + lft] = resb[pos + lft] = 1;
else add.emplace(pos, x);
while (!add.empty()) {
stk.push(add.top());
add.pop();
}
}
while (!stk.empty()) {
pair <int, int> tmp = stk.top();
resa[tmp.first] += (tmp.second >> 1) & 1;
resb[tmp.first] += (tmp.second) & 1;
stk.pop();
}
l = max(n, m) + k;
while (resa[l] == 0) l--;
for (int i = l; i >= 1; i--) putchar(resa[i] + '0');
putchar('\n');
l = max(n, m) + k;
while (resb[l] == 0) l--;
for (int i = l; i >= 1; i--) putchar(resb[i] + '0');
putchar('\n');
return 0;
}