Skip to content

2026夏个人训练赛第十八场

A. 幂次方

  • 数学
  • 数论

直接枚举底数统计。

cpp
#include <iostream>
#include <cmath>

using namespace std;

typedef long long LL;

const int N = 100010;
bool a[N];

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    LL n, l, res = 0;
    cin >> n;
    l = sqrt(n);
    for (int i = 2; i <= l; ++i) {
        if (a[i]) continue;
        for (LL j = i; j <= l; j *= i) {
            a[j] = true;
        }
        res += floor(log(n) / log(i) - 1);
    }
    cout << n - res << endl;
    return 0;
}

B. 自学

  • 二分查找
  • 贪心

二分答案,贪心验证,如果旷课更优优先旷课,否则就尽可能上课,还不够就旷其他课(不需要具体找到那个要旷的课),只需要记录次数即可,每次检查次数是否不大于 m。我刚开始不小心把 Long long 爆了

cpp
#include <iostream>
 
using namespace std;
 
typedef long long LL;
const int N = 300010;
 
int n;
LL a[N], b[N], m;
 
bool check(__int128_t mid) {
    __int128_t t = 0;
    for (int i = 1; i <= n; ++i) {
        if (b[i] >= a[i]) t += (mid + b[i] - 1) / b[i];
        else {
            if ((mid + a[i] - 1) / a[i] <= m) t += (mid + a[i] - 1) / a[i];
            else t += m + (mid - a[i] * m + b[i] - 1) / b[i];
        }
    }
    return t <= m * n;
}
 
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i) cin >> b[i];
    __int128_t l = 0, r = 1e18;
    while (l < r) {
        __int128_t mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    cout << (LL)l << endl;
    return 0;
}

C. 假期

  • 贪心
  • 数据结构
  1. 尽可能做收益最大的
  2. 尽可能晚做,给别的留机会

因为我们只在意左边的空位,可以直接用并查集维护,如果占用了就把父亲给左边的,查询根就是左边第一个空位。

cpp
#include <iostream>
#include <algorithm>
 
using namespace std;
 
typedef long long LL;
const int N = 100010;
pair<LL, LL> a[N];
int fa[N];

int getfa(int x) {
    if (x < 0) return 0;
    return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}
 
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i].second >> a[i].first;
    }
    sort(a + 1, a + n + 1, greater<pair<LL, LL>>());
    LL res = 0;
    for (int i = 1; i <= m; ++i) fa[i] = i;
    for (int i = 1; i <= n; ++i) {
        auto [x, y] = a[i];
        int p = getfa(m - y + 1);
        if (p) {
            res += x;
            int q = getfa(p - 1);
            fa[p] = q;
        }
    }
    cout << res << endl;
    return 0;
}

D. 排队

我一直 wa(待补)

E. 传送

  • 图论
  • 广度优先搜索
  • 最短路

只有四种可能

1 → n
1 → 未确定点 → 未确定边 → n
1 → 未确定边 → 未确定点 → n
1 → 未确定边 → 未确定点 → 未确定边 → n

正反分别跑一遍 bfs 维护一下即可。

cpp
#include <iostream>
#include <algorithm>
#include <queue>
#include <cstring>
 
using namespace std;
 
const int N = 300010;
int head[N], ver[N * 2], ne[N * 2], tot;
int d1[N], d2[N], v1[N], v2[N], f1[N], f2[N];

void add(int x, int y) {
    ver[++tot] = y, ne[tot] = head[x], head[x] = tot;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        int x, y;
        cin >> x >> y;
        add(x, y), add(y, x);
    }
    memset(d1, 0x3f, sizeof(d1));
    memset(d2, 0x3f, sizeof(d2));
    queue<int> q;
    d1[1] = 0;
    v1[1] = 1;
    q.emplace(1);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = head[x]; i; i = ne[i]) {
            int y = ver[i];
            if (!v1[y]) {
                v1[y] = 1;
                d1[y] = d1[x] + 1;
                if (y) q.emplace(y);
            }
            if (y == 0 && d1[y] == d1[x] + 1) {
                f1[x] = true;
            }
        }
    }
    d2[n] = 0;
    v2[n] = 1;
    q.emplace(n);
    while (!q.empty()) {
        int x = q.front();
        q.pop();
        for (int i = head[x]; i; i = ne[i]) {
            int y = ver[i];
            if (!v2[y]) {
                v2[y] = 1;
                d2[y] = d2[x] + 1;
                if (y) q.emplace(y);
            }
            if (y == 0 && d2[y] == d2[x] + 1) {
                f2[x] = true;
            }
        }
    }
    int dis = d1[n];
    for (int i = 1; i <= n; ++i) {
        int res = min(dis, min(d1[0] + d2[i] - f1[i], min(d1[i] + d2[0] - f2[i], d1[0] + d2[0] - f1[i] - f2[i])));
        // cout << dis << ' ' << d1[0] + d2[i] - f1[i] << ' ' << d1[i] + d2[0] - f2[i] << endl;
        cout << (res == 0x3f3f3f3f ? -1 : res) << ' ';
    }
    cout << '\n';
    return 0;
}

F. Paintball

没什么知识含量的计算几何,竟有 11 min 就敲完了的,肯定是 codex 发力了…

G. Islands

  • 图论
  • 并查集

反着做,用并查集动态维护连通块数量,按照高度排序,然后根据查询分批加入更低的点。

cpp
#include <iostream>
#include <algorithm>
#include <tuple>
 
using namespace std;

const int dx[] = {0, 1, 0, -1}, dy[] = {1, 0, -1, 0};
const int N = 1010, M = 100010;
int a[N][N], fa[N * N], t[M], res[M], cnt;
tuple<int, int, int> b[N * N];
int n, m;

int get(int x, int y) {
    return (x - 1) * m + y;
}

int getfa(int x) {
    return x == fa[x] ? x : fa[x] = getfa(fa[x]);
}

void merge(int x, int y) {
    x = getfa(x), y = getfa(y);
    if (x != y) fa[y] = x, cnt--;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> m;
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= m; ++j) {
                cin >> a[i][j];
                b[get(i, j)] = {a[i][j], i, j};
                fa[get(i, j)] = get(i, j);
            }
        }
        sort(b + 1, b + n * m + 1);
        int q;
        cin >> q;
        for (int i = 1; i <= q; ++i) {
            cin >> t[i];
        }
        cnt = 0;
        for (int i = q, j = n * m; i; --i) {
            while (j && get<0>(b[j]) > t[i]) {
                auto [_, x, y] = b[j--];
                cnt++;
                int p = get(x, y);
                for (int k = 0; k < 4; ++k) {
                    int tx = x + dx[k], ty = y + dy[k];
                    if (tx > 0 && tx <= n && ty > 0 && ty <= m && a[tx][ty] > t[i]) {
                        int q = get(tx, ty);
                        merge(p, q);
                    }
                }
            }
            res[i] = cnt;
        }
        for (int i = 1; i <= q; ++i) cout << res[i] << ' ';
        cout << endl;
    }
    return 0;
}

H. 旅游巴士

  • 图论
  • 最短路
  • 动态规划

就是个分层图最短路,走不动了只需要假定来之前等了几个 k 即可。

cpp
#include <iostream>
#include <cstring>
#include <queue>

using namespace std;

typedef long long LL;
const int N = 10010, M = 110;
LL dis[N][M], t[N * 2];
bool vis[N][M];
int head[N], ver[N * 2], ne[N * 2], tot;

struct Node {
    LL d;
    int x, p;

    bool operator <(const Node &_) const {
        return d > _.d;
    }
};

void add(int x, int y, int tt) {
    ver[++tot] = y, ne[tot] = head[x], head[x] = tot, t[tot] = tt;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    int n, m, k;
    cin >> n >> m >> k;
    for (int i = 1; i <= m; ++i) {
        int x, y, z;
        cin >> x >> y >> z;
        add(x, y, z);
    }
    memset(dis, 0x3f, sizeof(dis));
    priority_queue<Node> q;
    dis[1][0] = 0;
    q.emplace(Node({0, 1, 0}));
    while (!q.empty()) {
        auto [_, x, p] = q.top();
        q.pop();
        if (vis[x][p]) continue;
        vis[x][k] = true;
        for (int i = head[x]; i; i = ne[i]) {
            int y = ver[i];
            LL d = dis[x][p] + 1 + max(0LL, (t[i] - dis[x][p] + k - 1) / k * k);
            if (dis[y][d % k] > d) {
                dis[y][d % k] = d;
                q.emplace(Node({dis[y][d % k], y, int(d % k)}));
            }
        }
    }
    cout << (dis[n][0] == 0x3f3f3f3f3f3f3f3f ? -1 : dis[n][0]) << endl;
    return 0;
}

J. Counting regions

没人开的题

  • I. Protecting lawn
  • K. Playing games
  • L. Permuting cows
  • M. Calculating sums
  • N. Decoding graphs