2025 暑期 mx 集训 7.21

T1

https://www.luogu.com.cn/problem/P1122

题意

给你一棵树,每个点有点权,求任选一个连通块的最大点权和。

\(n\leq 10^6\)

Solution

直接树形 dp 即可。

\(f_i\) 表示 \(i\) 子树内能达到的最大权值。

转移:\(f_u = f_u + \max_{v\in son_u}\{f_v, 0\}\)

Code

#include <bits/stdc++.h>

using namespace std;

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

int n, a[N], f[N];
vector<int> e[N];

void dfs(int u, int fa)
{
    f[u] = a[u];
    for (auto v : e[u]) {
        if (v == fa) continue;
        dfs(v, u);
        f[u] += max(0, f[v]);
    }
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i < n; i++) {
        int u, v; cin >> u >> v;
        e[u].push_back(v), e[v].push_back(u);
    }
    dfs(1, 0);
    int ans = 0;
    for (int i = 1; i <= n; i++) ans = max(ans, f[i]);
    cout << ans;
    return 0;
}

T2

https://www.luogu.com.cn/problem/P7299

题意

\(n\) 个人,起初第 \(i\) 个人站在位置 \(i\) 上。

\(q\) 次操作,每次操作给你 \(a,b\),表示交换位置 \(a\) 上与位置 \(b\) 上的人。

然后重复执行无限轮这 \(q\) 次操作,求每个点能到达多少不同的点。

\(n\leq 10^5, q\leq 2\times 10^5\)

Solution

首先考虑执行 \(n\) 次后,所有人都回到原位。

所以可以 \(O(nq)\)

然后考虑执行完一轮之后,假设每个位置上的人为 \(pos_i\)

那么 \(i\)\(pos_i\) 接下来的路径是一样的。

于是可以把他们合并到一起,看作一个整体。

我们还可以维护出每个人经过了哪些点,于是我们就可以把每个整体里的每个人所经过的点都扔到 set 里来统计答案。

合并可以用并查集实现。

Code

#include <bits/stdc++.h>

using namespace std;

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

int n, k, p[N], pos[N];
vector<int> e[N];
set<int> q[N];

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

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> n >> k;
    for (int i = 1; i <= n; i++) p[i] = pos[i] = i, e[i].push_back(i);
    for (int i = 1; i <= k; i++) {
        int a, b; cin >> a >> b;
        e[pos[b]].push_back(a);
        e[pos[a]].push_back(b);
        swap(pos[a], pos[b]);
    }
    for (int i = 1; i <= n; i++) {
        int x = fifa(i), y = fifa(pos[i]);
        if (x != y) p[x] = y;
    }
    for (int i = 1; i <= n; i++) for (auto v : e[i]) q[fifa(i)].insert(v);
    for (int i = 1; i <= n; i++) cout << q[fifa(i)].size() << "\n";
    return 0;
}

T3

https://www.luogu.com.cn/problem/P9377

题意

给你 \(2^k\) 个点和 \(m\) 条边。

\(u,v,w\) 表示一条无向边连接 \((u,v)\) 边权为 \(w\)

然后还有一个数组 \(a_i\)。当 \(u,v\) 的二进制上有 \(j\) 位不同时,他们可以通过花费 \(a_j\) 的代价到达。

\(s\) 到其他所有点的最短路。

\(k \leq 18\)

Solution

首先考虑可以暴力建边然后最短路。

接着考虑建一些虚点来辅助转移。

\((u, i, j)\) 表示考虑二进制下前 \(i\) 位,改了 \(j\) 位,改成的二进制数为 \(u\)

有以下连边:

  • \((u, 0, 0) \overset{w}{\rightarrow} (v, 0, 0)\)
  • \((u, i, j) \overset{0}{\rightarrow} (u, i + 1, j)\) 要保证 \(i < k\)
  • \((u, i, j) \overset{0}{\rightarrow} (u \oplus 2^i, i + 1, j + 1)\) 要保证 \(i < k\)
  • \((u, k, j) \overset{a_j}{\rightarrow} (u, 0, 0)\)

首先 \(1,2\) 种连边是好理解的。

至于第三种可能有疑问:他第 \(i\) 位都改变了,为啥边权还是 \(0\)

因为你是从 \(u\) 变成了其他的,并不是 \(u\),所以你边权是 \(0\)。然后你还可以把他和最后一个一块看,这样就对了。

然后现在点数是 \(nk^2\),边数是 \(m + nk^2\)

直接跑 dijkstra 是 \(mk + nk^3\) 过不去。

于是考虑我们有很多的 \(0\) 权变,那可以类似 01 bfs 对于这些 \(0\) 权边我们可以直接拿出来,bfs 直接更新。

这是 \(O(n)\) 的。于是需要用优先队列维护的边数变为 \(O(m + nk)\)

总时间复杂度 \(O(mk + nk^2 + nk)\)

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

const int K = 18, N = (1 << K);

int k, m, s, a[N];
ll d[N];
vector<pair<int, int>> e[N];
bool vis[N], v[N][K][K];

struct node {
    int p, d;
    bool operator < (const node &b) const { return d > b.d; }
};
priority_queue<node> pq;

struct Node { int u, i, j; };

void bfs(int x)
{
    queue<Node> q;
    auto ach = [&](int x, int i, int j) -> void {
        if (v[x][i][j]) return;
        v[x][i][j] = 1;
        q.push({x, i, j});
    };
    ach(x, 0, 0);
    while (!q.empty()) {
        auto t = q.front(); q.pop();
        int u = t.u, i = t.i, j = t.j;
        if (i < k) {
            ach(u, i + 1, j);
            ach(u ^ (1 << i), i + 1, j + 1);
        } else {
            if (d[u] > d[x] + a[j]) {
                d[u] = d[x] + a[j];
                pq.push({u, d[u]});
            }
        }
    }
}

void dijkstra()
{
    memset(d, 0x3f, sizeof d);
    d[s] = 0;
    pq.push({s, 0});
    while (!pq.empty()) {
        int u = pq.top().p; pq.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (auto t : e[u]) {
            int v = t.first, w = t.second;
            if (d[v] > d[u] + w) {
                d[v] = d[u] + w;
                pq.push({v, d[v]});
            }
        }
        bfs(u);
    }
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    cin >> k >> m >> s;
    for (int i = 1; i <= k; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) {
        int u, v, w; cin >> u >> v >> w;
        e[u].emplace_back(v, w), e[v].emplace_back(u, w);
    }
    dijkstra();
    for (int i = 0; i < (1 << k); i++) cout << d[i] << " ";
    return 0;
}

T4

https://www.luogu.com.cn/problem/CF1535F

题意

给你 \(n\) 个字符串。

对于字符串 \(a,b\) 你可以进行以下操作:

  • 选择其中任意一个字符串的子串,并将其从小到大排序。

定义 \(f(a,b)\) 为进行上面的操作使得 \(a = b\) 的最小操作次数。

如果无解则 \(f(a, b) = 1337\)

\(\sum_{i = 1}^n \sum_{j = i + 1}^n f(s_i, s_j)\)

\(n\leq 2\times 10^5,\sum |s_i| \leq 2\times 10^5, |s_1|=|s_2|=\cdots=|s_n|\)

Solution

有好多种做法。

一个类似根号分治,因为 \(n |s|\leq 2\times 10^5\)

一个二维数点。这我没看懂。

还有一个比较简单的做法,就是我写的这个。

首先考虑 \(f\) 的值。

只有 \(0,1,2,1337\) 这些取值。

  • 如果字符集不一样那就是 \(1337\)
  • 如果直接相等了,那就是 \(0\)
  • 直接都全排一遍序就是 \(2\)
  • 这个必须满足两个串除了 \(\text{LCP}\)\(\text{LCS}\) 的部分外有其中一个串是保证升序的。

\(\text{LCS}\) 是最长公共后缀)

然后 \(1337,2,0\) 都是好算的,考虑 \(1\) 的个数怎么算。

首先就是把这些串排序,然后你 \(i\) 往后的 \(\text{LCP}\) 是不升的。

然后我们可以对于每个串找到他的极长上升子串。

例如:abcadebc 有:abcadebc

考虑我们用单调栈维护一段区间,他们和 \(i\)\(\text{LCP}\) 长度相等。

然后我们就确定了前缀,考虑中间这部分,我们可以直接二分求出。

那么现在就是数一段区间内一段后缀和 \(i\) 相同的字符串数量。

这个我们可以对每个反串建立一颗 trie 树。然后就可以轻松维护。

具体的我们可以对于每个位置维护有哪些字符串,然后直接在这个位置里二分即可。

Code

#include <bits/stdc++.h>

using namespace std;

#define ll long long

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

int cnt;
vector<int> id[N], dc[N];
int ch[N][27], lcp[N];

vector<int> add(string s, int k)
{
    int n = s.size(), p = 1;
    vector<int> pos(n + 1);
    id[p].push_back(k);
    pos[n] = 1;
    for (int i = n - 1; i >= 0; i--) {
        int c = s[i] - 'a';
        if (!ch[p][c]) ch[p][c] = ++cnt;
        p = ch[p][c];
        id[p].push_back(k); // 这个位置上有哪些字符串
        pos[i] = p; // 这个位置在 tire 上的点
    }
    return pos;
}

#define low(k, c) lower_bound(id[k].begin(), id[k].end(), c)

int query(int k, int l, int r) { return low(k, r) - low(k, l); }

ll solve(vector<string> a)
{
    cnt = 1; // 因为 trie 里 pos 初值为 1 所以从 1 开始。
    int n = a.size(), m = a[0].size();
    sort (a.begin(), a.end());
    for (int i = 1; i <= n * m + 2; i++) {
        id[i].clear();
        for (int j = 0; j < 26; j++) ch[i][j] = 0;
    }
    vector<vector<int>> pos(n);
    for (int i = 0; i < n; i++) pos[i] = add(a[i], i);
    lcp[0] = 0;
    for (int i = 1; i < n; i++) { // 维护相邻的 lcp 长度
        int j = 0;
        while (j < m && a[i - 1][j] == a[i][j]) j++;        
        lcp[i] = j;
    }
    for (int i = 0; i < n; i++) { // 找极长的上升子串的结尾
        dc[i].clear();
        for (int j = 1; j < m; j++)
            if (a[i][j - 1] > a[i][j])
                dc[i].push_back(j);
        dc[i].push_back(m);
    }
    ll ans = 1ll * n * (n - 1); // 有 (n * (n - 1)) / 2 对,每对的贡献是 2
    vector<pair<int, int>> st; // 单调栈
    st.emplace_back(n, -1);
    for (int i = n - 1; i >= 0; i--) {
        for (int j = 1; j < st.size(); j++) {
            int l = st[j].first, r = st[j - 1].first; // 后面直接差分了,所以这里 l 没有 + 1
            int p = st[j].second;
            if (p < m) {
                p = *upper_bound(dc[i].begin(), dc[i].end(), p);
                ans -= query(pos[i][p], l, r);
            } else ans -= 2 * (r - l); // 这就是两个串相等的情况
        }
        while (st.back().second >= lcp[i]) st.pop_back();
        st.emplace_back(i, lcp[i]);
    }
    return ans;
}

int main()
{
    cin.tie(0)->ios::sync_with_stdio(false);
    int n; cin >> n;
    map<string, vector<string>> mp;
    for (int i = 1; i <= n; i++) {
        string s; cin >> s;
        string t = s;
        sort (t.begin(), t.end());
        mp[t].push_back(s);
    }
    ll ans = 0, sum = 0;
    for (auto t : mp) {
        auto v = t.second;
        ans += sum * v.size() * 1337;
        ans += solve(v);
        sum += v.size();
    }
    cout << ans;
    return 0;
}
posted @ 2025-08-08 19:05  Dtwww  阅读(11)  评论(0)    收藏  举报