2025 暑期 mx 集训 7.31

T1

https://www.mxoj.net/problem/P110080?contestId=87

题意

\(n\) 个数 \(a_i\)

你要选出两个集合,使得每个集合里的极差 \(\leq k\)

\(n\leq 5\times 10^5\)

Solution

简单题,先排序,然后扫一遍二分求贡献,然后前缀后缀取个 \(\max\) 枚举中间点。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 5e5 + 10, inf = 0x3f3f3f3f;

int n, k, a[N], pre[N], suf[N];

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    sort (a + 1, a + n + 1);
    for (int i = 1; i <= n; i++) {
        int l = 1, r = i - 1, p = i;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (a[mid] >= a[i] - k) r = (p = mid) - 1;
            else l = mid + 1;
        }
        pre[i] = max(pre[i - 1], i - p + 1);
    }
    for (int i = 1; i <= n; i++) {
        int l = i + 1, r = n, p = i;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (a[mid] <= a[i] + k) l = (p = mid) + 1;
            else r = mid - 1;
        }
        suf[i] = max(suf[i + 1], p - i + 1);
    }
    int ans = 0;
    for (int i = 1; i < n; i++) ans = max(ans, pre[i] + suf[i + 1]);
    cout << ans;
    return 0;
}

T2

https://www.mxoj.net/problem/P110081?contestId=87

题意

给你一个网格,左右移动耗费 \(1\) 的时间,上下移动耗费 \(k\) 的时间。有些格子无法通行。你要保证从 \((sx, sy) \to (tx, ty)\) 的最短路恰好耗费 \(s\) 的时间。

其中 \(s,k\) 可以是小数。你要求出最小的满足要求的 \(k\)

Solution

这个题因为要求的是最短路,当时我就列了一个式子:\(d + kx = s\),然后我肯定尽可能让 \(x\) 大,那 \(k\) 就小了。

但是在这种情况下,一定有一条使用 \(k\) 更少的路径长度肯定比 \(s\) 小。这才是最短路。

然后注意到 \(k\) 有单调性,所以直接二分答案跑最短路看长度和 \(s\) 的大小关系就行。

Code

#include <bits/stdc++.h>

using namespace std;

const int N = 1e2 + 10, inf = 0x3f3f3f3f;

int n, m, a[N][N];
int sx, sy, tx, ty;
double d[N][N];
bool vis[N][N];
int fx[] = { 0, 0, 1, -1 };
int fy[] = { 1, -1, 0, 0 };
double res;

struct node {
    int x, y;
    double d;
    bool operator < (const node &b) const {
        return d > b.d;
    }
};

void dijkstra(double k)
{
    priority_queue<node> q;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) d[i][j] = 1e9, vis[i][j] = 0;
    d[sx][sy] = 0; q.push({sx, sy, 0});
    while (!q.empty()) {
        int x = q.top().x, y = q.top().y; q.pop();
        if (vis[x][y]) continue;
        vis[x][y] = 1;
        for (int i = 0; i < 4; i++) {
            int dx = x + fx[i], dy = y + fy[i];
            if (dx < 1 || dx > n || dy < 1 || dy > m || a[dx][dy]) continue;
            double w = (i < 2 ? 1.0 : k);
            if (!(d[dx][dy] <= d[x][y] + w + (1e-4))) {
                d[dx][dy] = d[x][y] + w;
                q.push({dx, dy, d[dx][dy]});
            }
        }
    }
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> m >> sx >> sy >> tx >> ty;
    for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cin >> a[i][j];
    cin >> res;
    double l = 0, r = res;
    while (r - l > 1e-4) {
        double mid = (l + r) / 2;
        dijkstra(mid);
        if (res <= d[tx][ty] + (1e-4)) r = mid;
        else l = mid;
    }
    cout << fixed << setprecision(3) << l + (5e-5);
    return 0;
}

T3

https://www.mxoj.net/problem/P110082?contestId=87

题意

给你一个长度为 \(n\) 的字符串。

你要把这个字符串分成 \(k\) 个长度为 \(x\) 的子串,以及一个长度为 \(m\) 的串。

满足 \(kx + m = n\)\(m < x\)\(m\) 可以为 \(0\)

然后你就把这些长度为 \(x\) 的串任意重排然后连接起来。求对于每个 \(x\) 能组合出多少种不同的字符串,把所有 \(x\) 的答案加起来输出。对 \(998244353\) 取模。

\(n \leq 3\times 10^5\)

Solution

首先考虑 \(x\) 的取值是 \([1, n]\) 的,然后就可以枚举 \(x\),然后如果有 \(m\) 的话,可以枚举长为 \(m\) 的这个串的开头,然后算贡献。

一开始卡在如何快速算贡献上了,但是很快想到多重集排列,于是 \(n^3\) 就拿到了。

接着考虑实际上它每次是跳 \(x\) 的,这个复杂度总共是 \(O(n \ln n)\) 的,于是套上 hash 就有了一个 \(O(n^2 \ln n)\) 的做法。有 50 分。

但是我写出来答案大了,我就猜是算重了。如果把所有长为 \(x\) 的子串拿出来看作一个集合,那么如果有两个集合重了,则就去掉其中一个的贡献。这个可以用随机赋权的哈希来解决。

题面中说了,所有长为 \(x\) 的子串得连续,所以就不存在 \(m\) 把一个 \(x\) 分搁开的情况。

所以枚举 \(m\) 的位置也是 \(\frac{n}{x}\) 的。

然后好像是维护前后缀,目前还没搞明白。

T4

https://www.mxoj.net/problem/P110083?contestId=87

题意

给你一颗树,求有多少路径 \((u,v)\) 满足路径上的最大值和最小值均出现在端点处。

\(n\leq 5\times 10^5\)

Solution

暴力点分治,\(O(n\log^2 n)\) 卡过。我不会。

考虑类似 Kruskal 重构树。对最小生成树跑一个重构树,对最大生成树跑一个重构树。

那么现在符合条件的 \((u, v)\) 相当于在一棵树上,\(u\)\(v\) 的祖先,在另一颗树上,\(v\)\(u\) 的祖先。

那这样就好维护了。先跑一个 dfs 序。

我们遍历第一颗树,把从根到当前这个点的这条链上的点全在树状数组里 \(+1\)

这个只要在进来的时候加上,出去的时候减掉就可完成这个操作。

然后查询当前点的子树和,就是关于这个点的答案。

由于我们算上了每个点和他自己,题目中不让算,所以最后 \(-n\) 就行。

时间复杂度 \(O(n\log n)\)

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

const int N = 5e5 + 10, inf = 0x3f3f3f3f;

int n, p[N], L[N], R[N], cnt;
ll c[N], ans;
vector<int> e[N], g[N], G[N];

int fifa(int x) { return p[x] == x ? x : p[x] = fifa(p[x]); }

#define lb(o) ((o) & (-o))

void add(int x, int v) { for (; x <= n; x += lb(x)) c[x] += v; }

ll qry(int x) { ll res = 0; for (; x; x -= lb(x)) res += c[x]; return res; }

void dfs(int u)
{
    L[u] = ++cnt;
    for (auto v : g[u]) dfs(v);
    R[u] = cnt;
}

void dfs1(int u)
{
    add(L[u], 1);
    ans += qry(R[u]) - qry(L[u] - 1);
    for (auto v : G[u]) dfs1(v);
    add(L[u], -1);
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        e[u].push_back(v), e[v].push_back(u);
    }
    for (int i = 1; i <= n; i++) p[i] = i;
    for (int i = 1; i <= n; i++) {
        for (auto v : e[i]) {
            if (v >= i) continue;
            int x = fifa(i), y = fifa(v);
            p[y] = x;
            g[x].push_back(y);
        }
    }
    for (int i = 1; i <= n; i++) p[i] = i;
    for (int i = n; i >= 1; i--) {
        for (auto v : e[i]) {
            if (v <= i) continue;
            int x = fifa(i), y = fifa(v);
            p[y] = x;
            G[x].push_back(y);
        }
    }
    dfs(n);
    dfs1(1);
    cout << ans - n;
    return 0;
}
posted @ 2025-08-05 12:02  Dtwww  阅读(18)  评论(0)    收藏  举报