【做题笔记】CF1528C Trees of Tranquillity
Problem
题目大意:
给你两棵 \(n\) 个点的树,构建一个由 \(n\) 个点构成的无向图 \(G\),使得 \(u,v\) 在 \(G\) 中有边当且仅当在第一棵树中 \(u,v\) 中某一个是另一个的祖先,而在第二棵树中 \(u,v\) 中任意一个都不是另一个的祖先。
求图 \(G\) 的最大团。
Solution
若一个点集是 \(G\) 的最大团,则必须满足的条件是这些点在第一棵树上是一条链上的某些点, 且这条链的一个端点是另一个端点的祖先。
这个条件可以变得更宽一些,发现若一条链满足条件,那么把他深度较小的端点一直延伸到根上也是没关系的。
接下来考虑第二个树中的条件。转化一下其实就是选了一个点之后就不能选他子树中的点。
对于第一棵树,可以直接 dfs,并记录从他一直道根的路径上的信息,相当于要支持插点和删点操作。现在就要从中取一些点满足第二棵树的条件。
一个经典 trick 是把点转换成区间,那么相当于选出的区间不能相交或包含。
这个东西看起来好像不大可以动态插入删除?
其实观察一下发现,任意两个区间都是相离或包含,这个特殊性质非常有用。
考虑两个区间 \(a,b\),如果区间 \(a\) 包含区间 \(b\),那么选区间 \(b\) 一定比选区间 \(a\) 更优。所以可以考虑用一个 set 维护选择的区间,每次插入一个区间的时候把包含他的区间删掉,并且如果有区间被他包含就不插入。
这样解决了插入操作,删除呢?很简单,插入一个区间的时候记一下删除的区间,当删除这个区间的时候再把那个区间添加回来即可。
Code
//Think twice,code once.
#include<set>
#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int T,n,cnt,ans,l[300005],r[300005];
struct graph {
int tot,hd[300005];
int nxt[300005],to[300005];
void clear(int n) {
tot=0;
for (int i=1;i<=n;i++) hd[i]=0;
return ;
}
void add(int u,int v) {
nxt[++tot]=hd[u];
hd[u]=tot;
to[tot]=v;
return ;
}
}tr1,tr2;
set<pair<int,int>>s;
void init(int now) {
l[now]=++cnt;
for (int i=tr2.hd[now];i;i=tr2.nxt[i]) init(tr2.to[i]);
r[now]=cnt;
return ;
}
void dfs(int now) {
int flag=1;
pair<int,int>dlt={0,0};
auto it=s.lower_bound({l[now],r[now]});
if (it!=s.end()&&l[now]<it->first&&it->second<=r[now]) flag=0;
else {
if (it!=s.begin()) {
--it;
if (it->first<l[now]&&r[now]<=it->second) dlt=*it,s.erase(it);
}
s.emplace(l[now],r[now]);
}
ans=max(ans,(int)s.size());
for (int i=tr1.hd[now];i;i=tr1.nxt[i]) dfs(tr1.to[i]);
if (flag) {
s.erase({l[now],r[now]});
if (dlt!=make_pair(0,0)) s.insert(dlt);
}
return ;
}
int main() {
scanf("%d",&T);
while (T--) {
scanf("%d",&n);
tr1.clear(n);tr2.clear(n);
for (int i=2;i<=n;i++) {
int fa;
scanf("%d",&fa);
tr1.add(fa,i);
}
for (int i=2;i<=n;i++) {
int fa;
scanf("%d",&fa);
tr2.add(fa,i);
}
cnt=ans=0;
init(1);
dfs(1);
printf("%d\n",ans);
}
return 0;
}