题目
思路
树建模+LCA倍增+前缀和
PS:一道很好的训练题,能够学到很多技巧和知识
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 200043;
const int L = 20;
int tin[2 * N];
int tout[2 * N];
int p[2 * N];//记录节点i的父节点p[i];
int idx[N];//记录idx[i],数字i在哪个idx[i]个Tower
int dept = 0;//用来记录欧拉序列的深度
int dp[2 * N][L];//用来进行LCA的倍增算法,dp[i][j]表示节点i往上的第2^j个祖先节点
vector<int> g[N * 2];//用于记录树节点的child list
void dfs(int root, int father) {
//题意说明最后m-1个查询会将m个Tower合并成最后一个Tower
tin[root]=dept++;
dp[root][0] = father;
p[root] = father;
for (int i = 1; i < L; i++) {
dp[root][i] = dp[dp[root][i - 1]][i - 1];
}
for (auto child:g[root]) {
dfs(child, root);
}
tout[root]=dept++;
}
bool isAncestor(int x, int y) {
return tin[x] <= tin[y] && tout[x] >= tout[y];
}
int lca(int x, int y) {
if (isAncestor(x, y))
return x;
for (int i = L - 1; i >= 0; i--) {
if (!isAncestor(dp[x][i], y)) {
x = dp[x][i];
}
}
return p[x];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n, m;
cin >> n >> m;
for (int i = 0; i < n; i++) {
cin >> idx[i];
idx[i]--;
}
vector<int> cur(m, 0);
for (int i = 0; i < m; i++) cur[i] = i;
for (int i = 0; i < m - 1; i++) {
int x, y;
cin >> x >> y;
x--;
y--;
int nidx = i + m;
g[nidx].push_back(cur[x]);
g[nidx].push_back(cur[y]);
cur[x] = nidx;//合并后的新id
}
//树上LCA 记录dp[i][j] 节点i的第2^j个父节点
int root = 2 * m - 2;
dfs(root, root);
vector<int> psum(m, 0);
for (int i = 0; i < n - 1; i++) {
int t = lca(idx[i], idx[i + 1]);//求两个连续节点所在的Tower的最近公共祖先
if (t < m) {
psum[0]++;//pair<i,i+1>在同一个tower里面,不需要移动
} else psum[t - m + 1]++;
}
for (int i = 0; i < m - 1; i++) {
psum[i + 1] += psum[i];
}
for (int i = 0; i < m; i++) {
cout << n - 1 - psum[i] << endl;
}
return 0;
}