Codeforces1528 C. Trees of Tranquillity(欧拉序性质,树链dfs,set+分类讨论维护最大不相交线段数)

题意:

在这里插入图片描述

解法:
容易发现团在树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){//判断x是否是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;//存(L[x],x),自动按照L[x]从小到大排序
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()){//x在最左边
        int rc=it->second;
        if(!isfa(x,rc)){//和右边不相交
            ma++;
        }
    }else if(it==s.end()){//x在最右边
        it--;
        int lc=it->second;
        if(!isfa(lc,x)){//和左边不相交
            ma++;
        }
    }else{//x在中间
        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){//此时如果左右相交,x才有贡献
                ma++;
            }
        }else if(!fl&&fr){//和右边相交时一定没贡献
            ;
        }else if(fl&&fr){//和右边相交时一定没贡献
            ;
        }
    }
	s.insert({L[x],x});
}
void dfs1(int x){
	int temp=ma;//temp记录ma,回溯的时候恢复,保证ma是当前树链最值
	add(x);//插入线段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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值