Codeforces 1528C - Trees of Tranquillity (dfs, 树剖,dfs序)
思路
方法1
在树1上dfs,可以确保访问的点是树链,满足性质1;假设把访问到的点都在树2上标记,并将标记点之间的路径缩短到只有一条边,这样就构成一颗新树。而满足性质2的最大个数就是新树的叶子结点个数。只要我们能维护好叶子结点的个数,其中最大值就是答案。
使用树剖,就可以动态查询插入或删除结点是否增加或减少了叶子结点个数。
时间复杂度:\(O(n\log^2{n})\)
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 5e5 + 10;
const double eps = 1e-5;
int son[N], siz[N], h[N], nxt[N], dep[N], p[N], fa[N], top[N], cnt, rnk[N], dfn[N], si;
ll sum[N];
int n;
int ans;
bool vis[N];
int tarr[N];
int lowbit(int x) {
return x&-x;
}
int get(int p) {
int res = 0;
while(p) {
res += tarr[p];
p -= lowbit(p);
}
return res;
}
void Add(int p, int val) {
while(p <= n) {
tarr[p] += val;
p += lowbit(p);
}
}
void addval(int l, int r, int val) {
Add(l, val);
Add(r + 1, -val);
}
void dfs1(int o) {
son[o] = -1;
siz[o] = 1;
for (int j = h[o]; j; j = nxt[j])
if (!dep[p[j]]) {
dep[p[j]] = dep[o] + 1;
sum[p[j]] = sum[o] + 1;
fa[p[j]] = o;
dfs1(p[j]);
siz[o] += siz[p[j]];
if (son[o] == -1 || siz[p[j]] > siz[son[o]]) son[o] = p[j];
}
}
void dfs2(int o, int t) {
top[o] = t;
cnt++;
dfn[o] = cnt;
rnk[cnt] = o;
if (son[o] == -1) return;
dfs2(son[o], t); // 优先对重儿子进行 DFS,可以保证同一条重链上的点 DFS 序连续
for (int j = h[o]; j; j = nxt[j])
if (p[j] != son[o] && p[j] != fa[o]) dfs2(p[j], p[j]);
}
void add(int u, int v) {
si++;
nxt[si] = h[u];
h[u] = si;
p[si] = v;
}
void upd(int p, int val) {
while(p) {
int l = dfn[top[p]], r = dfn[p];
addval(l, r, val);
p = fa[top[p]];
}
}
int getid(int p) {
while(p) {
int l = dfn[top[p]], r = dfn[p];
if(get(l)) {
while(l <= r) {
int mid = (l + r) / 2;
if(get(mid)) l = mid + 1;
else r = mid - 1;
}
p = rnk[r];
break;
}
p = fa[top[p]];
}
return p;
}
int que(int p) {
return get(dfn[p]);
}
vector<int> np[N];
void solve(int p, int fa, int num) {
bool flag = false;
if(!que(p)) {
int tar = getid(p);
if(!tar || (que(tar) - vis[tar] >= 1)) flag = true;
}
upd(p, 1);
vis[p] = 1;
ans = max(ans, num + flag);
for(int nt : np[p]) {
if(nt == fa) continue;
solve(nt, p, num + flag);
}
upd(p, -1);
vis[p] = 0;
}
int main() {
IOS;
int t;
cin >> t;
while(t--) {
cin >> n;
cnt = 0;
si = 0;
for(int i = 1; i <= n; i++) {
np[i].clear();
dep[i] = 0;
vis[i] = 0;
tarr[i] = 0;
h[i] = 0;
}
for(int i = 2; i <= n; i++) {
int f;
cin >> f;
np[i].push_back(f);
np[f].push_back(i);
}
for(int i = 2; i <= n; i++) {
int f;
cin >> f;
add(f, i);
}
dfs1(1);
dfs2(1, 1);
ans = 0;
solve(1, 0, 0);
cout << ans << endl;
}
}
方法2
官方题解的方法确实巧妙。
从方法1知道,最大个数就是叶子结点个数。假设当前维护了一个叶子结点集\(S\),新加入的结点为\(v\)。
只有\(\forall u\in S, u既不是v的祖先也不在v的子树中\),v才可以被加入到\(S\)中。若\(v\)在\(u\)的子树中,用\(v\)替换\(u\)一定是最优的,所以加点策略如下:
- 若\(v\)是\(u\)的祖先,忽略\(v\);
- 若\(v\)在\(u\)的子树中,用\(v\)替换\(u\);
- 否则,将\(v\)加入\(S\)中。
如何判断\(v\)和\(u\)的关系,可以在预处理一个\(st_i\)和\(ft_i\),分别代表dfs过程中第一次访问\(i\)点的时间和最后一次访问的时间,那么
- 若\(v\)是\(u\)的祖先,\(st_v\le st_u, ft_v \ge ft_u\)。
- 若\(v\)在\(u\)的子树中,\(st_v\ge st_u, ft_v \le ft_u\)。
对于第一种情况,将\(\{st[u], u\}\)插入集合。判断时,找到第一个(保证和v最近)\(st[u] \ge st[v]\),再check \(ft[u]\le ft[v]\),如果是,说明\(v\)是\(u\)的祖先。
第二种情况同理。
#include <bits/stdc++.h>
#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define FILE freopen(".//data_generator//in.txt","r",stdin),freopen("res.txt","w",stdout)
#define FI freopen(".//data_generator//in.txt","r",stdin)
#define FO freopen("res.txt","w",stdout)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;
using namespace std;
/*-----------------------------------------------------------------*/
ll gcd(ll a, ll b) {return b ? gcd(b, a % b) : a;}
#define INF 0x3f3f3f3f
const int N = 5e5 + 10;
const double eps = 1e-5;
typedef pair<int, int> PII;
int st[N], ft[N];
vector<int> np1[N];
vector<int> np2[N];
set<PII> ps;
int ans;
int ti;
void dfs1(int p, int f) {
st[p] = ++ti;
for(int nt : np2[p]) {
if(nt == f) continue;
dfs1(nt, p);
}
ft[p] = ++ti;
}
void solve(int p, int f) {
bool ise = false;
PII keep;
auto tar = ps.lower_bound({st[p], 0});
if(tar == ps.end() || ft[p] <= ft[tar->second]) {
tar = ps.upper_bound({st[p], 0});
if(tar != ps.begin()) {
tar--;
int pre = tar->second;
if(ft[p] <= ft[tar->second]) {
keep = *tar;
ps.erase(tar);
ise = true;
}
}
ps.insert({st[p], p});
}
ans = max(ans, (int)ps.size());
for(int nt : np1[p]) {
if(nt == f) continue;
solve(nt, p);
}
if(ps.count({st[p], p})) ps.erase({st[p], p});
if(ise) ps.insert(keep);
}
int main() {
IOS;
int t;
cin >> t;
while(t--) {
int n;
cin >> n;
ps.clear();
for(int i =1 ; i<= n; i++) {
np1[i].clear();
np2[i].clear();
}
for(int i = 2; i <= n; i++) {
int f;
cin >> f;
np1[i].push_back(f);
np1[f].push_back(i);
}
for(int i = 2; i <= n; i++) {
int f;
cin >> f;
np2[i].push_back(f);
np2[f].push_back(i);
}
ti = 0;
ans = 0;
dfs1(1, 0);
solve(1, 0);
cout << ans << endl;
}
}