20230413 训练记录:最小生成树
删除一段使得剩下数是 15 倍数的方案数
注意到 \(15n\) 必定以 \(0/5\) 结尾,因此有两种情况:
设 \(n\) 的数位和为 \(s\)。
- 末尾是 \(0/5\),要删除的是中间某一段和为 \(s \bmod 3\) 的一段。
- 否则删除的是后缀,遇到每一个 \(0/5\) 时,判断前缀是否是 \(3\) 的倍数。
没 OJ 就在这里交一下好啦
#include <bits/stdc++.h>
using ll = long long;
int main() {
std::cin.tie(nullptr)->sync_with_stdio(false);
std::string s;
std::cin >> s;
int n = s.size();
ll sum = 0;
std::vector<int> a(n);
for (int i = 0; i < n; i++) {
a[i] = s[i] - '0';
sum += a[i];
}
assert(n == 1 || a[0] != 0);
ll ans = 0;
if (int last = a.back(); last == 0 || last == 5) {
int need = (3 - last % 3) % 3;
std::unordered_map<int, int> counter{};
for (int i = 0, prefix = 0; i < n - 1; i++) {
ans += counter[need];
prefix = (prefix + a[i]) % 3;
counter[prefix] += 1;
}
} else {
for (int i = 0, prefix = 0; i < n; i++) {
prefix = (prefix + a[i]) % 3;
if (a[i] == 0 && prefix == 0) {
ans += 1;
} else if (a[i] == 5 && prefix == 1) {
ans += 1;
}
}
}
std::cout << ans << '\n';
return 0;
}
公路修建 / Prim 模板题
完全没学过 Prim,但是 OIwiki 上毛估估看一眼大概是贪心加最近的点,复杂度是 \(\mathcal O(n^2)\)。
展开代码
#include <bits/stdc++.h>
using ll = long long;
using db = double;
const int N = 5010;
int n, vis[N];
ll dis[N];
struct point {
ll x, y;
ll dist(const point &_) const {
return (x - _.x) * (x - _.x) + (y - _.y) * (y - _.y);
}
} points[N];
void prim() {
memset(dis, 0x3f, sizeof dis);
dis[1] = 0;
for (int i = 1; i <= n - 1; i++) {
int x = 0;
for (int j = 1; j <= n; j++) {
if (!vis[j] && (!x || dis[j] < dis[x])) {
x = j;
}
}
vis[x] = true;
for (int j = 1; j <= n; j++) {
if (!vis[j]) {
ll nd = points[x].dist(points[j]);
dis[j] = std::min(dis[j], nd);
}
}
}
}
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld%lld", &points[i].x, &points[i].y);
}
prim();
double ans = 0;
for (int i = 1; i <= n; i++) {
ans += std::sqrt(1. * dis[i]);
}
printf("%.2lf\n", ans);
return 0;
}
P2573 [SCOI2012] 滑雪
\(n\) 个点 \(m\) 条边,点权表示点的高度,边权表示距离。只能从高处向低(非严格)处 “滑行”,可以传送到曾经走过的点。问最多能去多少点以及最小 “滑行” 距离和。
第一问容易,主要是第二问。
大一的时候就听过这个 “回退” 的说法:2021牛客寒假算法基础集训营 6。即走过的连通块再访问无花费,要最小花费的话就是最小生成树。
但另一个限制,只能从高向低处走该怎么解决?按照高度从大到小排就可以啦~ 这样就能保证在不使用传送的情况下一条路径上的点权是不上升的。
展开代码
#include <bits/stdc++.h>
using ll = long long;
const int N = 100010, M = 2000010;
int n, m, cnt, _cnt, h[N], height[N];
ll sum;
struct edge {
int u, v, w, t;
bool operator< (const edge &_) const {
return height[v] != height[_.v] ? height[v] > height[_.v] : w < _.w;
}
} edges[M];
void link(int u, int v, int w) {
edges[++_cnt] = { u, v, w, h[u] }, h[u] = _cnt;
}
int vis[N];
void dfs(int u) {
if (vis[u]) return;
vis[u] = 1;
for (int i = h[u]; i; i = edges[i].t) {
dfs(edges[i].v);
}
}
int p[N];
int find(int x) {
return p[x] = p[x] == x ? x : find(p[x]);
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++) {
scanf("%d", height + i);
}
for (int i = 1, u, v, w; i <= m; i++) {
scanf("%d%d%d", &u, &v, &w);
if (height[u] >= height[v]) link(u, v, w);
if (height[v] >= height[u]) link(v, u, w);
}
dfs(1);
int ans = std::count(vis + 1, vis + n + 1, 1);
printf("%d ", ans);
std::iota(p, p + n + 1, 0);
std::sort(edges + 1, edges + 1 + _cnt);
for (int i = 1; i <= _cnt; i++) {
int u = edges[i].u, v = edges[i].v, w = edges[i].w;
if (vis[u] && vis[v]) {
int fu = find(u), fv = find(v);
if (fu != fv) {
sum += w;
p[fv] = fu;
cnt += 1;
}
if (cnt == ans - 1) break;
}
}
printf("%lld\n", sum);
return 0;
}
今天好怠惰啊。。。