VP Educational Codeforces Round 72 (Rated for Div. 2)
A. Creating a Character
题意:给你\(a, b, c\),把\(c\)分给\(a, b\),求\(a > b\)的方案数。
可以二分分给多少个给\(a\)。
点击查看代码
void solve() {
int a, b, c;
std::cin >> a >> b >> c;
if (a + c <= b) {
std::cout << 0 << "\n";
return;
}
int l = 0, r = c;
while (l < r) {
int mid = l + r >> 1;
if (a + mid > b + c - mid) {
r = mid;
} else {
l = mid + 1;
}
}
std::cout << std::max(0, c - l + 1) << "\n";
}
B. Zmei Gorynich
题意:\(n\)个技能,每个技能造成\(d_i\)点伤害,如果用完这个技能怪物血量大于\(0\)则恢复\(h_i\)的血量,否则游戏结束。怪物一开始有\(x\)血,每个技能随便用。求打败怪物最少使用几次技能。
枚举一直用一个技能,打到怪物血少于最高伤害的技能,然后用这个技能打败怪物。
点击查看代码
void solve() {
i64 n, x;
std::cin >> n >> x;
std::vector<std::pair<i64, i64>> a(n);
i64 max = 0;
for (int i = 0; i < n; ++ i) {
std::cin >> a[i].first >> a[i].second;
max = std::max(max, a[i].first);
}
if (max >= x) {
std::cout << 1 << "\n";
return;
}
i64 ans = 1e18;
for (auto & [d, h] : a) {
if (d <= h) {
continue;
}
ans = std::min(ans, 1 + (x - max + d - h - 1) / (d - h));
}
if (ans == 1e18) {
ans = -1;
}
std::cout << ans << "\n";
}
C. The Number Of Good Substrings
题意:给你一个二进制,求有多少子串满足子串长度等于其代表的数。
如果没有前导零,观察到子串长度大于\(20\)就一定大于字符串长度,于是我们可以暴力枚举。但因为有前导零,我们需要记录当前位置下一个1在哪里,每次从跳到这个1开始枚举。
点击查看代码
void solve() {
std::string s;
std::cin >> s;
int n = s.size();
std::vector<int> next(n + 1);
next[n] = n;
for (int i = n - 1; i >= 0; -- i) {
next[i] = next[i + 1];
if (s[i] == '1') {
next[i] = i;
}
}
int ans = 0;
for (int i = 0; i < n; ++ i) {
int sum = 0;
for (int j = next[i]; j < n && sum <= n; ++ j) {
sum = sum * 2 + (s[j] - '0');
ans += sum == j - i + 1;
}
}
std::cout << ans << "\n";
}
D. Coloring Edges
题意:给一个有向图的边染色,使得没有一个环的颜色都一样。求方案。
显然如果没环就是一个颜色。
如果有环,一个环最多两个颜色,且每个环并不影响其它环,所以最多两个颜色。关于方案,如果一条边是小的点连大的点,则染色为1,否则染色为2。因为环里必然有这两种类型的边,所以合法。
点击查看代码
void solve() {
int n, m;
std::cin >> n >> m;
std::vector<std::vector<int>> adj(n);
std::vector<int> ans(m), in(n);
for (int i = 0; i < m; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
if (u < v) {
ans[i] = 1;
} else {
ans[i] = 2;
}
adj[u].push_back(v);
++ in[v];
}
std::queue<int> q;
for (int i = 0; i < n; ++ i) {
if (in[i] == 0) {
q.push(i);
}
}
int cnt = 0;
while (q.size()) {
int u = q.front(); q.pop();
++ cnt;
for (auto & v : adj[u]) {
if ( -- in[v] == 0) {
q.push(v);
}
}
}
if (cnt != n) {
std::cout << 2 << "\n";
} else {
std::cout << 1 << "\n";
std::ranges::fill(ans, 1);
}
for (int i = 0; i < m; ++ i) {
std::cout << ans[i] << " \n"[i == m - 1];
}
}
E. Sum Queries?
题意:给你一个数组,两种操作,一个是改变一个数的值,一个是询问\([l, r]\)的和最小的坏子序列的和。对于一个子序列,记\(sum\)为它的所有元素的和,如果在十进制下每一位都至少有一个元素和和相同,这个子序列就是好,如果有一位不满足,就是坏的。
显然需要有一位至少出现了进位才可能使得这一位是坏的。发现选两个数产生进位就一定是坏的。于是猜测最多选两个数。那么对于某一位,我们需要选两个数满足这一位都非零。
那么只需要给每一位开一个线段树维护最小值和次小值就行。
点击查看代码
#define ls (u << 1)
#define rs (u << 1 | 1)
#define umid (tr[u].l + tr[u].r >> 1)
template <class Info>
struct Node {
int l, r;
Info info;
};
template <class Info>
struct SegmentTree {
std::vector<Node<Info> > tr;
SegmentTree(){}
SegmentTree(int _n) {
init(_n);
}
SegmentTree(std::vector<Info> & a) {
init(a);
}
void init(int _n) {
tr.assign(_n << 2, {});
build(0, _n - 1);
}
void init(std::vector<Info> & a) {
int _n = (int)a.size();
tr.assign(_n << 2, {});
build(0, _n - 1, a);
}
void pushup(int u) {
tr[u].info = tr[ls].info + tr[rs].info;
}
void build(int l, int r, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
return;
}
int mid = l + r >> 1;
build(l, mid, ls); build(mid + 1, r, rs);
}
void build(int l, int r, std::vector<Info> & a, int u = 1) {
tr[u] = {l, r, {}};
if (l == r) {
tr[u].info = a[l];
return;
}
int mid = l + r >> 1;
build(l, mid, a, ls); build(mid + 1, r, a, rs);
pushup(u);
}
void modify(int p, Info add, bool set = false) {
int u = 1;
while (tr[u].l != tr[u].r) {
int mid = umid;
if (p <= mid) {
u = ls;
} else {
u = rs;
}
}
if (set) {
tr[u].info = add;
} else {
tr[u].info = tr[u].info + add;
}
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 = umid;
if (r <= mid) {
return query(l, r, ls);
} else if (l > mid) {
return query(l, r, rs);
}
return query(l, r, ls) + query(l, r, rs);
}
};
struct Info {
int min1, min2;
};
Info operator + (const Info & a, const Info & b) {
Info res{};
res.min1 = std::min(a.min1, b.min1);
res.min2 = std::min({std::max(a.min1, b.min1), a.min2, b.min2});
return res;
}
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];
}
i64 p[11]{};
p[0] = 1;
for (int i = 1 ; i <= 10; ++ i) {
p[i] = p[i - 1] * 10;
}
const int inf = 2e9;
std::vector<SegmentTree<Info>> tr(10);
std::vector<Info> info(n);
for (int i = 0; i < 10; ++ i) {
for (int j = 0; j < n; ++ j) {
int x = a[j] % p[i + 1] / p[i];
info[j] = {x == 0 ? inf : a[j], inf};
}
tr[i].init(info);
}
while (m -- ) {
int op;
std::cin >> op;
if (op == 1) {
int x, y;
std::cin >> x >> y;
-- x;
for (int i = 0; i < 10; ++ i) {
int v = y % p[i + 1] / p[i];
tr[i].modify(x, Info{v == 0 ? inf : y, inf}, true);
}
} else {
int l, r;
std::cin >> l >> r;
-- l, -- r;
int ans = -1;
for (int i = 0; i < 10; ++ i) {
auto [x, y] = tr[i].query(l, r);
if (x == inf || y == inf) {
continue;
}
if (ans == -1 || x + y < ans) {
ans = x + y;
}
}
std::cout << ans << "\n";
}
}
}