「多校训练」2021牛客G7-F. xay loves trees
xay loves trees
线段树、思维暴力、树上问题
题意
此题 的改版,区别是要求第一颗树选点连续。
思路
- 容易想到点集在第一课树上是一条链,然后根据第二颗树的 dfs 序关系来判断是否可选点,用线段树染色来做。
- 而选点是否冲突的依据是染色有无冲突,即最大染色值是否 > 2。
- 如果发生冲突考虑从链顶部开始撤销操作,每次撤销后恢复复杂度会被链加菊花卡炸,考虑优化。
- 每次冲突时,只从顶部删除一个,依然冲突就不记录现在链的答案,但依然保存链的节点个数。
- 因此每搜索到一个点,最多进行 4 次线段树操作,时间复杂度 \(O(nlogn)\)
证明正确性:若当前链出现非法,则新点集大小=原点集大小+1(加入新节点)-1(删除上端点)=原点集大小, 即新出现的非法情况的点集大小与原合法点集的大小是相同的,当最优解(即点集最大大小)更新时,肯定 在某处形成了合法解,因为只有合法解省去了-1(删除上端点)一步,才可能使得点集大小增加。 —— 陈奕翔 新昌县实验中学
const int N = 3e5 + 10;
vector<int> edges[2][N];
int ans = 0;
struct SegmentTree {
#define ls u << 1
#define rs u << 1 | 1
struct T {
int l, r;
ll v;
ll add;
}tr[N << 2];
void pushup(int u) {
tr[u].v = max(tr[ls].v, tr[rs].v);
}
void update(T& rt, int add) {
rt.add += add, rt.v += add;
}
void pushdown(int u) {
if (tr[u].add) {
update(tr[ls], tr[u].add);
update(tr[rs], tr[u].add);
tr[u].add = 0;
}
}
void build(int u, int l, int r) {
tr[u].l = l, tr[u].r = r, tr[u].v = tr[u].add = 0;
if (tr[u].l == tr[u].r) {
return ;
}
int mid = (tr[u].l + tr[u].r) >> 1;
build(ls, l, mid), build(rs, mid + 1, r);
pushup(u);
return ;
}
void modify(int u, int l, int r, int v) {
if (tr[u].l >= l && tr[u].r <= r) {
update(tr[u], v);
return ;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
if (l <= mid) modify(ls, l, r, v);
if (r > mid) modify(rs, l, r, v);
pushup(u);
return ;
}
int query(int u, int l, int r) {
if (tr[u].l >= l && tr[u].r <= r) {
return tr[u].v;
}
pushdown(u);
int mid = (tr[u].l + tr[u].r) >> 1;
int res = 0;
if (l <= mid) res = query(ls, l, r);
if (r > mid) res = max(res, query(rs, l, r));
return res;
}
} tr;
int dfn[N], tot, rk[N], sz[N], fa[N];
void dfs(int u, int p) {
sz[u] = 1;
dfn[u] = ++tot, rk[tot] = u;
for (auto v: edges[1][u]) {
if (v == p) continue;
fa[v] = u;
dfs(v, u);
sz[u] += sz[v];
}
}
vector<int> path;
bool st[N];
int n;
void dfs1(int u, int p, int tp, int cnt) {
path.pb(u);
vector<int> tmp;
bool ok = true;
tr.modify(1, dfn[u], dfn[u] + sz[u] - 1, 1);
if (tr.query(1, 1, n) > 1) {
tr.modify(1, dfn[path[tp]], dfn[path[tp]] + sz[path[tp]] - 1, -1);
tmp.pb(path[tp]);
tp++;
cnt--;
if (tr.query(1, 1, n) > 1) {
ok = false;
}
}
if (ok) {
ans = max(ans, cnt);
}
for (auto v: edges[0][u]) {
if (v == p) continue;
dfs1(v, u, tp, cnt + 1);
}
tr.modify(1, dfn[u], dfn[u] + sz[u] - 1, -1);
for (auto t: tmp) {
tp--;
tr.modify(1, dfn[t], dfn[t] + sz[t] - 1, 1);
}
path.pop_back();
return;
}
int main() {
int T;
re(T);
while (T--) {
tot = ans = 0;
re(n);
path.clear();
for (int i = 1; i <= n; i++) st[i] = false;
for (int j = 0; j < 2; j++)
for (int i = 1; i <= n; i++) edges[j][i].clear();
for (int j = 0; j < 2; j++) {
for (int i = 1; i < n; i++) {
int a, b;
re(a), re(b);
edges[j][a].pb(b), edges[j][b].pb(a);
}
}
tr.build(1, 1, n);
dfs(1, 0);
dfs1(1, 0, 0, 1);
printf("%d\n", ans);
}
return 0;
}