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 有:abc,ade,bc。
考虑我们用单调栈维护一段区间,他们和 \(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;
}

浙公网安备 33010602011771号