题意:
解法:
容易发现团在树1中,一定是根到某个叶子的树链,上面的点的子集.
还需要满足选定子集在树2中没有祖先关系.
祖先关系可以用树2的欧拉序判断,
如果x是v的祖先,那么L[x]<=L[v]且R[x]>=R[v].
将每个点的欧拉序看成线段[L[x],R[x]],
那么满足条件的子集,一定两两不相交,
不满足条件的子集,则存在线段包含线段的情况.
---
ps:欧拉序形成的线段,相交时一定是包含关系,一定不存在不包含的相交关系.
---
因此线段图一定是这样的(曲线代表线段的区间):
根到某个叶子的树链,上面的点看作若干线段,
问题变为选出其中的若干线段,满足两两不相交.
如何计算最大可选集合呢?
当我们新插入一条线段x时,设ma为插入之前的子集最大值,考虑插入x时如何维护最大线段数.
这里用的是set+分类讨论:
将线段按照左端点L排序,
对于当前线段x,分类讨论:
1.如果x是唯一一个线段,那么ma++
2.如果x是最左边线段,如果和右边线段不相交,那么ma++
3.如果x是最右边线段,如何和左边线段不相交,那么ma++
4.如果x是中间线段,设lc为左边线段,rc为右边线段,分类讨论:
(1)x和lc不相交,x和rc不相交,那么ma++
(2)x和rc相交,x和rc不相交,如果此时lc和rc相交,那么lc可以替换为x,ma++
(3)其他情况x和rc相交,此时一定没贡献.
这样就可以动态维护当前树链的最大值了,
在dfs到叶子的时候更新答案即可.
---
按L排序,以及找左边和右边线段,可以用set实现,只需要存(L[x],x)二元组即可.
也可以用权值线段树找前驱和后继.
---
code:
#include<bits/stdc++.h>
#define PI pair<int,int>
using namespace std;
const int maxm=2e6+5;
vector<int>g1[maxm];
vector<int>g2[maxm];
int L[maxm],R[maxm],idx;
int ans;
int n;
int isfa(int x,int y){
return L[x]<=L[y]&&R[x]>=R[y];
}
void dfs2(int x){
L[x]=++idx;
for(int v:g2[x])dfs2(v);
R[x]=idx;
}
set<PI>s;
int ma;
void add(int x){
if(s.empty()){
ma++;
s.insert({L[x],x});
return ;
}
auto it=s.lower_bound({L[x],0});
if(it==s.begin()){
int rc=it->second;
if(!isfa(x,rc)){
ma++;
}
}else if(it==s.end()){
it--;
int lc=it->second;
if(!isfa(lc,x)){
ma++;
}
}else{
auto pre=it;pre--;
int lc=pre->second,rc=it->second;
int fl=isfa(lc,x),fr=isfa(x,rc),flr=isfa(lc,rc);
if(!fl&&!fr){
ma++;
}else if(fl&&!fr){
if(flr){
ma++;
}
}else if(!fl&&fr){
;
}else if(fl&&fr){
;
}
}
s.insert({L[x],x});
}
void dfs1(int x){
int temp=ma;
add(x);
if(!g1[x].size()){
ans=max(ans,ma);
}
for(int v:g1[x])dfs1(v);
s.erase({L[x],x});
ma=temp;
}
void solve(){
cin>>n;
for(int i=1;i<=n;i++){
g1[i].clear();
g2[i].clear();
}
for(int i=2;i<=n;i++){
int fa;cin>>fa;
g1[fa].push_back(i);
}
for(int i=2;i<=n;i++){
int fa;cin>>fa;
g2[fa].push_back(i);
}
idx=0;
dfs2(1);
ans=ma=0;
dfs1(1);
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;cin>>T;while(T--)
solve();
return 0;
}