Luogu P7037 Solution
闲话
相比于 P3684 和 P4901,这道题对不同知识点的缝合更加自然。
P7037=ST 表/树链剖分/倍增+思维。
本人认为此题的难点在于推出多个点的 LCA 为 DFS 序最小和最大的点的 LCA。
解法
注意到,首先不考虑第二个问题,只考虑最少要砍多少条边,易得最优解法一定是砍与根节点相连的边。
那么考虑第二个问题,不难注意到就是要将本来砍与根节点相邻的边改为砍掉根节点直接子节点为根的每棵子树下的深度最大的节点使得该节点是所有被占节点的祖先。
那么考虑如何高效地维护这个信息。
容易发现整棵树被根节点分成了若干个不交的部分。对于这些不交的部分,我们使用 DFS 分别标记它们处在哪一个节点下辖的子树内。
接下来,利用 set
存储每个子树内的被占节点的 DFS 序,利用 set
自动排序的特性进行处理即可。
哈?你问我怎么求 LCA?既然我们都处理了 DFS 序……那当然要转换成 RMQ 问题啦!根据 DFS 序的性质,我们可以发现一个节点的祖先的 DFS 序一定严格小于这个节点的 DFS 序。于是在使用 ST 表 \(O(n\log n)\) 预处理后接下来的询问就都是 \(O(1)\) 了!
然而,询问完之后还是要丢进 set
里操作。
最终总时间复杂度为 \(O(n\log n)\),但是常数极大。
代码
#include <algorithm>
#include <cstdio>
#include <iostream>
#include <set>
#include <utility>
#include <vector>
using namespace std;
const int N = 1e5 + 10;
int n, m, x, eul[20][N << 1], dfn[N], rnk[N], idx1, siz[N], idx2, lg2[N << 1], ps[N], cl[N], ttsz, ttat, res1;
vector<int> road[N];
set<int> st[N];
char ch;
void dfs(int x)
{
dfn[x] = ++idx1;
ps[x] = ++idx2;
eul[0][idx2] = dfn[x];
rnk[dfn[x]] = x;
if (!road[x].size())
{
siz[x] = 1;
return;
}
for (auto &i : road[x])
{
dfs(i);
siz[x] += siz[i];
eul[0][++idx2] = dfn[x];
}
}
void clr(int x, int tcl)
{
cl[x] = tcl;
for (auto &i : road[x])
clr(i, tcl);
}
void init()
{
for (int i = 2; i <= idx2 + 1; i++)
lg2[i] = lg2[i >> 1] + 1;
for (int i = 1; i <= lg2[idx2]; i++)
{
for (int j = 1; j + (1 << i) - 1 <= idx2 + 1; j++)
{
eul[i][j] = min(eul[i - 1][j], eul[i - 1][(j + (1 << (i - 1)))]);
}
}
}
int lca(int x, int y)
{
x = ps[x], y = ps[y];
if (x > y)
swap(x, y);
int tmp = lg2[y - x + 1];
return rnk[min(eul[tmp][x], eul[tmp][y - (1 << tmp) + 1])];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 2; i <= n; i++)
{
scanf("%d", &x);
road[x].emplace_back(i);
}
dfs(1);
init();
for (auto &i : road[1])
clr(i, i);
for (int i = 1; i <= m; i++)
{
cin >> ch;
scanf("%d", &x);
if (ch == '+')
{
res1 += !st[cl[x]].size();
ttat++;
if (st[cl[x]].size())
ttsz -= siz[lca(rnk[*st[cl[x]].begin()], rnk[*st[cl[x]].rbegin()])];
st[cl[x]].emplace(dfn[x]);
ttsz += siz[lca(rnk[*st[cl[x]].begin()], rnk[*st[cl[x]].rbegin()])];
printf("%d %d\n", res1, ttsz - ttat);
continue;
}
ttat--;
ttsz -= siz[lca(rnk[*st[cl[x]].begin()], rnk[*st[cl[x]].rbegin()])];
st[cl[x]].erase(dfn[x]);
if (st[cl[x]].size())
ttsz += siz[lca(rnk[*st[cl[x]].begin()], rnk[*st[cl[x]].rbegin()])];
res1 -= !st[cl[x]].size();
printf("%d %d\n", res1, ttsz - ttat);
}
}