Codeforces 1528C - Trees of Tranquillity (dfs, 树剖,dfs序)

Description

思路

方法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\)一定是最优的,所以加点策略如下:

  1. \(v\)\(u\)的祖先,忽略\(v\)
  2. \(v\)\(u\)的子树中,用\(v\)替换\(u\)
  3. 否则,将\(v\)加入\(S\)中。

如何判断\(v\)\(u\)的关系,可以在预处理一个\(st_i\)\(ft_i\),分别代表dfs过程中第一次访问\(i\)点的时间和最后一次访问的时间,那么

  1. \(v\)\(u\)的祖先,\(st_v\le st_u, ft_v \ge ft_u\)
  2. \(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;
    }
}
posted @ 2021-05-29 21:29  limil  阅读(163)  评论(0编辑  收藏  举报