Japan Registry Services (JPRS) Programming Contest 2025#2 (AtCoder Beginner Contest 415)
A - Unsupported Type
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::set<int> s;
for (int i =0 ; i < n; ++ i) {
int x;
std::cin >> x;
s.insert(x);
}
int x;
std::cin >> x;
if (s.count(x)) {
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 - Pick Two
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
std::string s;
std::cin >> s;
std::vector<int> ans;
for (int i = 0; i < s.size(); ++ i) {
if (s[i] == '#') {
ans.push_back(i + 1);
}
}
for (int i = 0; i < ans.size(); i += 2) {
std::cout << ans[i] << "," << ans[i + 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 - Mixture
题意:有\(n\)种物品,你每次选一个物品加入集合。给出有哪些状态是非法的,求能不能不经过非法状态拿完所有物品。
从\(0\)状态出发,如果一个状态\(i\)是合法,那么就枚举接下来加入\(j\),如果\(i | (1 << j)\)是合法的,就让\(st[i | (1 << j)] |= st[i]\)。则最后\(st[(1 << n) - 1] = 1\)则有解。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
s = "0" + s;
std::vector<int> st(1 << n);
st[0] = 1;
for (int i = 0; i < 1 << n; ++ i) {
if (s[i] == '0') {
for (int j = 0; j < n; ++ j) {
if (!(i >> j & 1) && s[i + (1 << j)] == '0') {
st[i + (1 << j)] |= st[i];
}
}
}
}
if (st[(1 << n) - 1] > 0) {
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;
}
D - Get Many Stickers
题意:有\(n\)元,\(m\)个物品,每个物品花费\(a_i\)然后获得\(b_i\)。求最多买多少物品。
记\(d_i = a_i - b_i\),那么我们选\(d_i\)小的更优。如果有\(d_j > d_i\)应该优先拿,如果\(a_j > a_i\),显然\(i\)比\(j\)优,否则\(a_j < a_i\),那么先拿\(i\)有机会拿\(j\),反之不能,而且损耗更大。
排序后模拟,能拿就拿。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
i64 n;
int m;
std::cin >> n >> m;
std::vector<std::tuple<i64, i64, i64>> a(m);
for (int i = 0; i < m; ++ i) {
i64 x, y;
std::cin >> x >> y;
a[i] = {x - y, x, y};
}
std::ranges::sort(a);
i64 ans = 0;
for (auto & [d, x, y] : a) {
i64 cnt = (std::max(0ll, n - (x - 1)) + d - 1) / d;
n -= d * cnt;
ans += cnt;
}
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;
}
E - Hungry Takahashi
题意:从第一步在\((1, 1)\)出发,要到\((n, m)\)。第\(k\)步走到\((i, j)\)获得\(a[i][j]\)减少\(p_k\)。设初始值为\(x\),那么需要找一条路径使得路径上的所有路径和都大于等于\(0\)。求最小的\(x\)。
走到\((i, j)\)的步数是固定的,为\(i - j - 1\)。那么给每个\(a[i][j]\)减去\(p_{i-j-1}\)。
问题就变的经典了。考虑二分\(x\),然后记\(f[i][j]\)表示到\((i, j)\)的最大值,注意如果\(f[i][j] < 0\)不可转移给\(f[i + 1][j], f[i][j + 1]\)。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
void solve() {
int n, m;
std::cin >> n >> m;
std::vector a(n + 1, std::vector<i64>(m + 1));
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
std::cin >> a[i][j];
}
}
std::vector<i64> h(n + m);
for (int i = 1; i <= n + m - 1; ++ i) {
std::cin >> h[i];
}
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
a[i][j] -= h[i - 1 + j - 1 + 1];
}
}
const i64 inf = 1e16;
auto check = [&](i64 x) -> bool {
std::vector f(n + 1, std::vector<i64>(m + 1, -inf));
for (int i = 1; i <= n; ++ i) {
for (int j = 1; j <= m; ++ j) {
if (i == 1 && j == 1) {
f[i][j] = x + a[i][j];
} else {
f[i][j] = std::max(f[i - 1][j], f[i][j - 1]) + a[i][j];
}
if (f[i][j] < 0) {
f[i][j] = -inf;
}
}
}
return f[n][m] >= 0;
};
i64 l = 0, r = inf;
while (l < r) {
i64 mid = l + r >> 1ll;
if (check(mid)) {
r = mid;
} else {
l = 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 - Max Combo
题意:一个字符串的值为相同字符的区间的最大长度。给你一个\(s\),两种操作,一种是修改一个位置的字符,一种是询问一个子区间的价值。
考虑线段树维护区间信息,我们需要维护\(l, r, pre, suf, max\)分别表示这个区间的左右边界,最长前缀相同区间,最长后缀相同区间,最长相同区间。那么区间合并是,根据左右区间相邻端点字符是否相同进行讨论,具体看代码。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
std::string s;
template <class Info>
struct SegmentTree {
struct Node {
int l, r;
Info info;
};
std::vector<Node> tr;
SegmentTree() {};
SegmentTree(int n) {
init(n);
}
SegmentTree(std::vector<Info> & info) {
init(info);
}
void init(int n) {
tr.assign(n << 2, {});
build(0, n - 1);
}
void init(std::vector<Info> & info) {
int n = info.size();
tr.assign(n << 2, {});
std::function<void(int, int, int)> build = [&](int l, int r, int u) -> void {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info = info[l];
return;
}
int mid = l + r >> 1;
build(l, mid, u << 1); build(mid + 1, r, u << 1 | 1);
pushup(u);
};
build(0, n - 1, 1);
}
void pushup(int u) {
tr[u].info = tr[u << 1].info + tr[u << 1 | 1].info;
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info.init(l, r);
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 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;
}
}
tr[u].info.init(tr[u].l, tr[u].r);
u >>= 1;
while (u) {
pushup(u);
u >>= 1;
}
}
Info query(int l, int r, int u = 1) {
if (l <= tr[u].l && tr[u].r <= r) {
return tr[u].info;
}
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) {
return query(l, r, u << 1);
} else if (l > mid) {
return query(l, r, u << 1 | 1);
}
return query(l, r, u << 1) + query(l, r, u << 1 | 1);
}
};
struct Info {
int l, r;
int pre, suf, max;
void init(int _l, int _r) {
l = _l;
r = _r;
pre = suf = max = 1;
}
};
Info operator + (const Info & l, const Info & r) {
Info res{};
res.l = l.l;
res.r = r.r;
res.max = std::max(l.max, r.max);
res.pre = l.pre;
res.suf = r.suf;
if (s[l.r] == s[r.l]) {
res.max = std::max(res.max, l.suf + r.pre);
if (l.pre == l.r - l.l + 1) {
res.pre = l.pre + r.pre;
}
if (r.suf == r.r - r.l + 1) {
res.suf = l.suf + r.suf;
}
}
res.max = std::max({res.max, res.pre, res.suf});
return res;
}
void solve() {
int n, q;
std::cin >> n >> q;
std::cin >> s;
SegmentTree<Info> tr(n);
while (q -- ) {
int op;
std::cin >> op;
if (op == 1) {
int i;
char x;
std::cin >> i >> x;
-- i;
s[i] = x;
tr.modify(i);
} else {
int l, r;
std::cin >> l >> r;
-- l, -- r;
std::cout << tr.query(l, r).max << "\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;
}
G - Get Many Cola
题意:和\(d\)差不多,不过改为花费最多的钱。同时\(1\leq b_i < a_i \leq 300, n \leq 10^{15}, m \leq 2\times 10^5\)。
考虑背包,\(f[i]\)表示有\(i\)元时的最大花费,那么背包有\(f[i - j + b_j] = \max_{j=1}^{i}(f[i - j + b_j], f[i] + b_j)\)。但背包体积太大,这时有一个\(trick\)是设置一个阈值\(K\),表示我们需要把\(n\)降到这个阈值一下才\(dp\),多余的我们贪心取。
如果贪心取\(i\),记\(d = a_i - b_i\),那么可以花费\(\lfloor \frac{n-K}{d} \rfloor \times b_i\),取最大的这个就行。
点击查看代码
#include <bits/stdc++.h>
using i64 = long long;
using i128 = __int128;
void solve() {
i64 n;
int m;
std::cin >> n >> m;
const int N = 300;
const i64 inf = 1e16;
std::vector<i64> A(N + 1, -inf);
for (int i = 0; i < m; ++ i ){
int a, b;
std::cin >> a >> b;
A[a] = std::max<i64>(A[a], b);
}
i64 B = 0, d = inf;
const int K = 100000;
for (int i = 1; i <= N; ++ i) {
if (A[i] < 0) {
continue;
}
//(n - k) / d * B >= (n - k) / d1 * B1
if (B == 0 || ((i128)n * A[i] * d > (i128)n * B * (i - A[i]))) {
B = A[i];
d = i - A[i];
}
}
i64 ans = n;
if (n > K) {
i64 cnt = (n - K + d - 1) / d;
ans += cnt * B;
n -= cnt * d;
}
int k = n;
std::vector<i64> f(k + 1, -inf);
f[k] = 0;
for (int i = k; i >= 1; -- i) {
for (int j = 1; j <= N && j <= i; ++ j) {
if (A[j] > 0) {
f[i - j + A[j]] = std::max(f[i - j + A[j]], f[i] + A[j]);
}
}
}
ans += *std::max_element(f.begin(), f.end());
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;
}