\(\color{purple}(1)\) P5478 [BJOI2015] 骑士的旅行
- 给定一颗 \(n\) 个节点的树。有 \(m\) 个骑士,最开始第 \(i\) 个骑士在 \(p_i\) 节点上,武力值为 \(f_i\)。接下来有 \(q\) 次操作 \((t_i, x_i, y_i)\):
- \(t_i = 1\),输出树上 \(x_i, y_i\) 路径上的前 \(k\) 大骑士的武力值。
- \(t_i = 2\),\(p_{x_i} \gets y_i\);
- \(t_i = 3\),\(f_{x_i} \gets y_i\)。
- \(n, m \le 4 \times 10^4\),\(q \le 8 \times 10^4\),\(\color{red}k \le 20\)。
显然需要树链剖分,将树上问题转化成序列上问题。
发现 \(k\) 很小,所以我们可以用线段树维护前 \(k\) 大,并用 \(\mathcal O(k)\) 的时间复杂度 pushup。
注意可用 multiset
存储每个叶子节点上的骑士编号和骑士武力值。
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 800010;
int n, m, f[N], p[N], q, k;
int id[N], idx, pos[N];
vector<int> vec[N];
vector<int> calc(vector<int> t) {
vector<int> res;
sort(t.begin(), t.end(), greater<int>());
for (int i = 0; i < t.size() && i < k; ++ i ) res.push_back(t[i]);
return res;
}
struct Node {
int l, r;
vector<int> V;
multiset<int, greater<int> > S;
}tr[N << 2];
struct Segment_Tree {
void pushup(int u) {
vector<int> res;
for (int t : tr[u << 1].V) res.push_back(t);
for (int t : tr[u << 1 | 1].V) res.push_back(t);
tr[u].V = calc(res);
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r;
if (l == r) {
l = pos[l];
for (int i = 0; i < vec[l].size(); ++ i ) {
tr[u].S.insert(vec[l][i]);
tr[u].V.push_back(vec[l][i]);
}
tr[u].V = calc(tr[u].V);
}
else {
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
void modify(int u, int x, int d) {
if (tr[u].l == tr[u].r) {
if (d > 0) tr[u].S.insert(d);
else {
tr[u].S.erase(tr[u].S.find(-d));
}
int cnt = 0;
tr[u].V.clear();
for (int t : tr[u].S) {
++ cnt;
if (cnt > k) break;
tr[u].V.push_back(t);
}
}
else {
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, d);
else modify(u << 1 | 1, x, d);
pushup(u);
}
}
vector<int> query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) return tr[u].V;
int mid = tr[u].l + tr[u].r >> 1;
vector<int> res;
if (l <= mid) {
for (int t : query(u << 1, l, r)) res.push_back(t);
}
if (r > mid) {
for (int t : query(u << 1 | 1, l, r)) res.push_back(t);
}
return calc(res);
}
}S;
struct Tree {
vector<int> g[N];
void add(int a, int b) { g[a].push_back(b); }
int fa[N], dep[N], sz[N], son[N], top[N];
void dfs1(int u, int f) {
fa[u] = f;
dep[u] = dep[f] + 1;
sz[u] = 1;
for (int v : g[u]) {
if (v == f) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int f) {
top[u] = f;
id[u] = ++ idx;
pos[idx] = u;
if (son[u]) {
dfs2(son[u], f);
for (int v : g[u])
if (v != fa[u] && v != son[u])
dfs2(v, v);
}
}
vector<int> query(int a, int b) {
vector<int> res;
while (top[a] != top[b]) {
if (dep[top[a]] < dep[top[b]]) swap(a, b);
for (int t : S.query(1, id[top[a]], id[a])) res.push_back(t);
res = calc(res);
a = fa[top[a]];
}
if (dep[a] > dep[b]) swap(a, b);
for (int t : S.query(1, id[a], id[b])) res.push_back(t);
return calc(res);
}
void modify(int x, int y) {
S.modify(1, id[x], y);
}
}T;
signed main() {
cin >> n;
for (int i = 1, u, v; i < n; ++ i ) {
cin >> u >> v;
T.add(u, v), T.add(v, u);
}
cin >> m;
for (int i = 1; i <= m; ++ i ) {
cin >> f[i] >> p[i];
vec[p[i]].push_back(f[i]);
}
cin >> q >> k;
T.dfs1(1, 0), T.dfs2(1, 0);
S.build(1, 1, n);
while (q -- ) {
int t, x, y;
cin >> t >> x >> y;
if (t == 1) {
auto res = T.query(x, y);
if (res.empty()) cout << "-1\n";
else {
for (int t : res) cout << t << ' ';
cout << '\n';
}
}
else if (t == 2) {
T.modify(p[x], -f[x]);
T.modify(y, f[x]);
p[x] = y;
}
else {
T.modify(p[x], -f[x]);
T.modify(p[x], y);
f[x] = y;
}
}
return 0;
}
\(\color{blue}(2)\) P6374 「StOI-1」树上询问
-
给定一棵 \(n\) 个点的无根树,有 \(q\) 次询问。
每次询问给一个参数三元组 \((a,b,c)\) ,求有多少个 \(i\) 满足这棵树在以 \(i\) 为根的情况下 \(a\) 和 \(b\) 的 LCA 为 \(c\) 。
-
\(n \le 5 \times 10^5\),\(q \le 2 \times 10^5\)。
模拟可知答案为当这棵树以 \(c\) 为根时除 \(a, b\) 所在子树内的点的数量,即 \(n - size_a - size_b\)。以及当 \(c\) 不在树上 \(a \sim b\) 的路径上时答案为 \(0\)。
所以我们需要解决两个问题:
-
判断 \(c\) 是否在 \(a \sim b\) 的路径上:
首先求出 \(\operatorname{lca}(a, b) = p\)。那么此时我们需要判断的就是是否 \(c\) 在 \(a \sim p\) 的路径上或 \(b \sim p\) 的路径上。即:
bool chk(int a, int b, int c) { int p = lca(a, b); return (lca(a, c) == c || lca(b, c) == c) && lca(c, p) == p; }
-
求当以 \(c\) 为根时,\(a\) 所在子树的大小:
分类讨论:
- 当 \(c\) 原来就是 \(a\) 的祖先时,做法是类似于 lca 的倍增往上跳,跳到某个点使得这个点的父亲是 \(a\);
- 否则,显然整棵树中,除了 \(c\) 所在的子树外,每个点都是 \(a\) 所在的子树,即答案为 \(n - size_c\)。
即:
int F(int a, int b) { for (int k = 19; ~k; -- k ) if (dep[fa[b][k]] > dep[a]) b = fa[b][k]; return b; } int calc(int a, int b) { if (a == b) return 0; if (lca(a, b) == b) return sz[F(b, a)]; return n - sz[b]; }
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, q;
vector<int> g[N];
int sz[N], fa[N][20], dep[N];
void dfs(int u, int f) {
dep[u] = dep[f] + 1, sz[u] = 1;
for (int v : g[u])
if (v != f) {
fa[v][0] = u;
for (int k = 1; k < 20; ++ k ) fa[v][k] = fa[fa[v][k - 1]][k - 1];
dfs(v, u);
sz[u] += sz[v];
}
}
int lca(int a, int b) {
if (dep[a] < dep[b]) swap(a, b);
for (int k = 19; ~k; -- k )
if (dep[fa[a][k]] >= dep[b]) a = fa[a][k];
if (a == b) return a;
for (int k = 19; ~k; -- k )
if (fa[a][k] != fa[b][k]) a = fa[a][k], b = fa[b][k];
return fa[a][0];
}
bool chk(int a, int b, int c) {
int p = lca(a, b);
return (lca(a, c) == c || lca(b, c) == c) && lca(c, p) == p;
}
int F(int a, int b) {
for (int k = 19; ~k; -- k )
if (dep[fa[b][k]] > dep[a]) b = fa[b][k];
return b;
}
int calc(int a, int b) {
if (a == b) return 0;
if (lca(a, b) == b) return sz[F(b, a)];
return n - sz[b];
}
int main() {
cin >> n >> q;
for (int i = 1, a, b; i < n; ++ i ) {
cin >> a >> b;
g[a].emplace_back(b);
g[b].emplace_back(a);
}
dfs(1, 0);
while (q -- ) {
int a, b, c;
cin >> a >> b >> c;
int res = 0;
if (!chk(a, b, c)) res = 0;
else res = n - calc(a, c) - calc(b, c);
cout << res << '\n';
}
return 0;
}
\(\color{green}(3)\) CF173B Chamber of Secrets
-
给定一张 \(n\times m\) 的包含
#
和.
的图,现有一束激光从左上角往右边射出。每次遇到
#
,你可以选择光线改变为上下左右四个方向之一,也可以不改变。求至少需要改变几次方向,可以使激光从第 \(n\) 行向右射出。
-
\(n, m \le 10^3\)。
显然总共有 \(4nm\) 种状态,即在每个位置有 \(4\) 种当前面对的方向。
发现转移是类似于图上的边,且边权仅有 \(0\) 和 \(1\)。所以 01bfs 即可。
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
const int N = 1010, M = 10000100;
int n, m;
char g[N][N];
struct Node {
int a, b, dx, dy;
bool operator <(const Node &h) const {
if (a == h.a) {
if (b == h.b) {
if (dx == h.dx) return dy < h.dy;
return dx < h.dx;
}
return b < h.b;
}
return a < h.a;
}
};
std::deque<Node> q;
int dis[N][N][3][3];
int main() {
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++ i ) scanf("%s", g[i] + 1);
memset(dis, 0x3f, sizeof dis);
q.push_back({1, 1, 0, 1});
dis[1][1][1][2] = 0;
const std::vector<int> tx({1, 0, -1, 0}), ty({0, -1, 0, 1});
while (!q.empty()) {
int x = q.front().a, y = q.front().b, dx = q.front().dx, dy = q.front().dy;
q.pop_front();
if (g[x][y] == '#') {
for (int i = 0; i < 4; ++ i ) {
if (dis[x][y][tx[i] + 1][ty[i] + 1] > 1e9) {
dis[x][y][tx[i] + 1][ty[i] + 1] = dis[x][y][dx + 1][dy + 1] + 1;
q.push_back({x, y, tx[i], ty[i]});
}
}
}
if (x + dx >= 1 && x + dx <= n && y + dy >= 1 && y + dy <= m && dis[x + dx][y + dy][dx + 1][dy + 1] > 1e9) {
dis[x + dx][y + dy][dx + 1][dy + 1] = dis[x][y][dx + 1][dy + 1];
q.push_front({x + dx, y + dy, dx, dy});
}
}
printf("%d\n", dis[n][m][1][2] < 1e9 ? dis[n][m][1][2] : -1);
return 0;
}
\(\color{orange}(4)\) CF1063B Labyrinth
-
给定一张 \(n\times m\) 的包含
*
和.
的图,*
是不能经过的障碍。给定你的起点 \((r,c)\),每次你可以往上下左右四个方向之一移动一步。
限制了你的向左移动的次数不超过 \(x\) 和向右移动的次数不超过 \(y\),求你能到达多少个格子。
-
\(n, m \le 2 \times 10^3\)。
枚举终点。可以发现如果确定了往右走的步数和最终到达的与起点的列数的差,就可以轻易的求出需要往左走的步数。
所以我们可以预处理出从起点到达每个点所需要的最小的往左次数,然后枚举终点判断合法即可。
$\color{blue}\text{Code}$
#include <bits/stdc++.h>
const int N = 2024;
int n, m, sx, sy, x, y;
char g[N][N];
int f[N][N]; // 到达 (i, j) 的最小向左次数
const std::vector<int> dx({0, 1, 0, -1}), dy({1, 0, -1, 0});
signed main() {
scanf("%d%d%d%d%d%d", &n, &m, &sx, &sy, &x, &y);
for (int i = 1; i <= n; ++ i ) scanf("%s", g[i] + 1);
memset(f, 0x3f, sizeof f);
std::list<std::pair<int, int> > q;
q.push_back({sx, sy});
f[sx][sy] = 0;
while (q.size()) {
int x = q.front().first, y = q.front().second;
q.pop_front();
for (int i = 0; i < 4; ++ i ) {
int a = x + dx[i], b = y + dy[i];
if (a >= 1 && a <= n && b >= 1 && b <= m && g[a][b] == '.' && f[a][b] > f[x][y] + (i == 2)) {
f[a][b] = f[x][y] + (i == 2);
if (i == 2) q.push_back({a, b});
else q.push_front({a, b});
}
}
}
int res = 0;
for (int i = 1; i <= n; ++ i )
for (int j = 1; j <= m; ++ j )
if (g[i][j] == '.' && f[i][j] < 1e9)
res += f[i][j] <= x && j - (sy - f[i][j]) <= y;
printf("%d\n", res);
return 0;
}