VP Educational Codeforces Round 54 (Rated for Div. 2)
A. Minimizing the String
题意:删除一个字符使得字符串字典序最小。
经典结论,删第一个\(s_i > s_{i+1}\)。
点击查看代码
void solve() {
int n;
std::cin >> n;
std::string s;
std::cin >> s;
for (int i = 0; i + 1 < n; ++ i) {
if (s[i] > s[i + 1]) {
std::cout << s.substr(0, i) + s.substr(i + 1) << "\n";
return;
}
}
std::cout << s.substr(0, n - 1) << "\n";
}
B. Divisor Subtraction
题意:每次把\(n\)减去它的最小质因子,直到零,求操作次数。
显然偶数一直减\(2\),奇数减掉一个质因子后变成偶数,也一直减\(2\)。
那么我们找到第一个质因子就行。
点击查看代码
void solve() {
i64 n;
std::cin >> n;
int m = 1e5;
for (int i = 2; i <= n / i; ++ i) {
if (n % i == 0) {
std::cout << (n - i) / 2 + 1 << "\n";
return;
}
}
std::cout << 1 << "\n";
}
C. Meme Problem
题意:给你\(d\),找两个非负数\(a, b\),使得\(a+b = d, a\times b = d\)。
可得\(a\times(d - a) = d\)。得到方程\(a^2 - d\times a + d = 0\)。
解这个方程看有没有非负数解。
点击查看代码
void solve() {
std::cout << std::fixed << std::setprecision(12);
int d;
std::cin >> d;
//ad - a^2 = d;
//a^2 - ad + d = 0
if (d == 0) {
std::cout << "Y " << 0.0 << " " << 0.0 << "\n";
return;
}
double x = std::sqrt(d * d - 4 * d);
if ((d + x) > 0) {
std::cout << "Y " << (d + x) / 2 << " " << d - (d + x) / 2 << "\n";
} else if ((-d + x) > 0) {
std::cout << "Y " << (-d + x) / 2 << " " << d - (-d + x) / 2 << "\n";
} else {
std::cout << "N\n";
}
}
D. Edge Deletion
题意:给你一个图,删边直到边数小于等于\(k\)。使得这个图的最短路和原图的最短路相同的点最多。
最短路径树。
最短路径树就是\(dijkstra\)过程中记录每个点的前继边就行了。然后没在最短路径树上的边都可以删,如果还有删,就在树上\(dfs\)找\(k\)条边。
点击查看代码
void solve() {
int n, m, k;
std::cin >> n >> m >> k;
std::vector<std::vector<std::array<int, 3>>> adj(n);
std::vector<int> st(m);
for (int i = 0; i < m; ++ i) {
int u, v, w;
std::cin >> u >> v >> w;
-- u, -- v;
adj[u].push_back(std::array<int, 3>{v, w, i});
adj[v].push_back(std::array<int, 3>{u, w, i});
}
std::vector<int> pre(n, -1);
using PII = std::pair<i64, int>;
const i64 inf = 1e18;
std::priority_queue<PII, std::vector<PII>, std::greater<PII>> heap;
std::vector<i64> dist(n, inf);
dist[0] = 0;
heap.emplace(dist[0], 0);
while (heap.size()) {
auto [d, u] = heap.top(); heap.pop();
if (d != dist[u]) {
continue;
}
for (auto & [v, w, id] : adj[u]) {
if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w;
pre[v] = id;
heap.emplace(dist[v], v);
}
}
}
for (int i = 0; i < n; ++ i) {
if (pre[i] != -1) {
st[pre[i]] = 1;
}
}
k = std::min(n - 1, k);
std::vector<int> ans;
auto dfs = [&](auto & self, int u, int fa) -> void {
if (k == 0) {
return;
}
for (auto & [v, w, id] : adj[u]) {
if (v == fa || !st[id]) {
continue;
}
if (k == 0) {
return;
}
if (st[id]) {
ans.push_back(id);
-- k;
self(self, v, u);
}
}
};
dfs(dfs, 0, -1);
std::cout << ans.size() << "\n";
for (auto & x : ans) {
std::cout << x + 1 << " \n"[x == ans.back()];
}
}
E. Vasya and a Tree
题意:给你一棵树,每次给\(u_i\)节点的子树里距离它小于等于\(d_i\)的点加上\(x_i\)。最后求每个点的值。
考虑求出深度,那么每个点需要在它的子树的\([dep_u, dep_u + d_i]\)这个区间的点加上\(x_i\)。可以直接\(dfs\),到一个点就把它的操作加上,然后递归子树,离开的时候把操作撤销就行了。这是一个区间修改单点查询问题。可以用树状数组做。
点击查看代码
template <class T>
struct Fenwick {
int n;
std::vector<T> tr;
Fenwick(int _n) {
init(_n);
}
void init(int _n) {
n = _n;
tr.assign(_n + 1, T{});
}
void add(int x, const T &v) {
for (int i = x; i <= n; i += i & -i) {
tr[i] = tr[i] + v;
}
}
T query(int x) {
T res{};
for (int i = x; i; i -= i & -i) {
res = res + tr[i];
}
return res;
}
T sum(int l, int r) {
return query(r) - query(l - 1);
}
};
void solve() {
int n;
std::cin >> n;
std::vector<std::vector<int>> adj(n);
for (int i = 1; i < n; ++ i) {
int u, v;
std::cin >> u >> v;
-- u, -- v;
adj[u].push_back(v);
adj[v].push_back(u);
}
int m;
std::cin >> m;
std::vector<std::vector<std::pair<int, int>>> op(n);
for (int i = 0; i < m; ++ i) {
int v, d, x;
std::cin >> v >> d >> x;
-- v;
op[v].emplace_back(d, x);
}
std::vector<int> dep(n);
std::vector<i64> ans(n);
Fenwick<i64> tr(n + 1);
auto dfs = [&](auto & self, int u, int fa) -> void {
for (auto & [d, x] : op[u]) {
tr.add(dep[u], x);
tr.add(std::min(n, dep[u] + d) + 1, -x);
}
ans[u] = tr.query(dep[u]);
for (auto & v : adj[u]) {
if (v == fa) {
continue;
}
dep[v] = dep[u] + 1;
self(self, v, u);
}
for (auto & [d, x] : op[u]) {
tr.add(dep[u], -x);
tr.add(std::min(n, dep[u] + d) + 1, x);
}
};
dep[0] = 1;
dfs(dfs, 0, -1);
for (int i = 0; i < n; ++ i) {
std::cout << ans[i] << " \n"[i == n - 1];
}
}
F. Summer Practice Report
题意:你要构造\(n\)个字符串,每个字符串有\(x_i\)个\(T\)和\(y_i\)个\(F\)。然后把他们按顺序拼接为一个字符串。能不能使得这个字符串没有一个长度大于\(k\)的子串都是相同字符。
贪心加\(dp\)。
考虑\(dpx_i\)为前\(i\)个字符串结尾为\(T\)最短的连续个数。\(dpy_i\)表示\(F\)结尾的最短个数。
那么对于第\(i\)位,我们有\(dpx_{i-1} + x_i\)个\(T\),要把他们切割为合法的字符串,至少需要\(\lceil \frac{dpx_{i-1} + x_i}{k} \rceil - 1\)个\(F\)。如果\(y_i\)少于这个数无解。如果\(y_i\)等于这个数,\(dpx_i = dpx_{i-1} + x_i - (\lceil \frac{dpx_{i-1} + x_i}{k} \rceil - 1) \times k\)。否则\(dpx_i = 0\)。\(dpy_i\)的讨论同理。
点击查看代码
void solve() {
int n, k;
std::cin >> n >> k;
std::vector<i64> x(n), y(n);
for (int i = 0; i < n; ++ i) {
std::cin >> x[i];
}
for (int i = 0; i < n; ++ i) {
std::cin >> y[i];
}
i64 dpx = 0, dpy = 0;
for (int i = 0; i < n; ++ i) {
i64 xcnt = (dpx + x[i] + k - 1) / k - 1;
if (y[i] < xcnt) {
std::cout << "NO\n";
return;
}
i64 ndpx = y[i] > xcnt ? 0 : dpx + x[i] - xcnt * k;
i64 ycnt = (dpy + y[i] + k - 1) / k - 1;
if (x[i] < ycnt) {
std::cout << "NO\n";
return;
}
i64 ndpy = x[i] > ycnt ? 0 : dpy + y[i] - ycnt * k;
dpx = ndpx;
dpy = ndpy;
}
std::cout << "YES\n";
}