[题解] CF 1528C

博客介绍了如何利用欧拉序结合STL解决图论中的最大团问题。通过DFS计算欧拉序,然后用区间判断和集合操作实现O(1)时间复杂度的贪心策略,找出不相交的树链,达到最大化节点数量的目标。文章详细阐述了算法思路并提供了C++代码实现。
摘要由CSDN通过智能技术生成

[题解] CF 1528C 欧拉序+stl+贪心

这是我第一次做cf div2的E题(而且还是我最不擅长的图论),内心非常激动!
题目链接
这里需要解释一下什么是团(clique):

团是图的一个子图,并且这个子图任意两个点都有一条直接边相连。

我们很容易发现,这个最大团所包含的节点,一定在第一个树的同一条树链上。
那么,我们要如何快速判断在树1的点是否在树2有子孙关系呢?
答案是欧拉序
首先你要知道dfs序长什么样。
在这里插入图片描述
图片来自大佬Styx-ferryman的博客。
那么欧拉序长什么样呢?
在这里插入图片描述
图片来自大佬Styx-ferryman的博客。
这个玩意儿可以非常轻松地实现。
这个是针对树的魔改欧拉序
我们可以发现这个玩意儿有个非常好的性质:

如果 a a a b b b的祖宗节点,那么 l [ a ] < = l [ b ]   & &   r [ a ] > = r [ b ] l[a]<=l[b] \ \&\& \ r[a] >=r[b] l[a]<=l[b] && r[a]>=r[b]
如果 a a a的区间和 b b b的区间相交,那么必有 a [ l , r ] ∈ b [ l , r ] a[l,r]\in b[l,r] a[l,r]b[l,r] b [ l , r ] ∈ a [ l , r ] b[l,r]\in a[l,r] b[l,r]a[l,r]。反之,如果不相交,那么必有 a [ l , r ] ∩ b [ l , r ] = ϕ a[l,r]\cap b[l,r]= \phi a[l,r]b[l,r]=ϕ

O ( 1 ) O(1) O(1)判断,是不是很强?!
对于树1,上面的每个点可以对应树2上的同一点,从而对应一个区间 [ l , r ] [l,r] [l,r]
问题转化为,在所有的树链中找出一些点,使得这些点对应的区间不相交,并且点的数量最多。
我们可以边dfs树1,边用一个set维护线段。
详情看这篇文章
需要注意的是stl自带的 l o w e r _ b o u n d lower\_bound lower_bound。这玩意儿也是找第一个键值大于等于的元素地址。如果大过头了不存在则返回 . e n d ( ) .end() .end()
下面贴出代码和细节。

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define pii pair<int,int>
using namespace std;

const double eps = 1e-10;
const double pi = acos(-1.0);
const int maxn = 3e5 + 10;

int n;
int l[maxn],r[maxn];
vector<int> g1[maxn],g2[maxn];
int dfslock;
int cur,ans;
set<pii> st;

void init(){//初始化
	for(int i = 1; i <= n; i++) g1[i].clear(), g2[i].clear();
	dfslock = cur = ans = 0;
	st.clear();
}

void dfs2(int u){//计算欧拉序
	l[u] = ++dfslock;
	for(auto& v : g2[u]) dfs2(v);
	r[u] = dfslock;
}

bool having(int a, int b){//用欧拉序判断a是否是b的根节点
	return l[a] <= l[b] && r[a] >= r[b];
}

void add(int x){
	if(st.empty()){//集合为空,直接加
		st.insert({l[x],x});
		cur = 1;
		return;
	}
	auto rg = st.lower_bound({l[x],x});
	if(rg == st.begin()){//在最左边,和右不相交
		if(!having(x,rg->second)) cur++;
	}
	else if(rg == st.end()){//在最右边,和左不相交
		rg--;
		if(!having(rg->second,x)) cur++;
	}
	else{//在中间
		auto rrg = rg;//右边线段
		rg--;//左边线段
		if(!having(x,rrg->second)){//和右相交一定无贡献
			if(!having(rg->second,x)) cur++;//和左不相交
			else if(having(rg->second,rrg->second)) cur++;//和左相交,但是和右不相交,那么可以有贡献。
		}
	}
	//插入这条链上所有节点。
	st.insert({l[x],x});
}

void dfs1(int u){
	add(u);
	int tmp = cur;
	if(g1[u].empty()) ans = max(ans,cur);//搜到叶节点,更新一下答案
	for(auto& v : g1[u]){
		dfs1(v);
		cur = tmp;//回溯,撤销操作
	}
	st.erase({l[u],u});//回溯,撤销操作
}

void solve(){
	scanf("%d",&n);
	init();
	for(int i = 2; i <= n; i++){
		int x;
		scanf("%d",&x);
		g1[x].push_back(i);
	}
	for(int i = 2; i <= n; i++){
		int x;
		scanf("%d",&x);
		g2[x].push_back(i);
	}
	dfs2(1);//维护树2的欧拉序
	dfs1(1);//遍历树1,得出答案
	printf("%d\n",ans);
}

int main()
{
	int t;
	scanf("%d",&t);
	while(t--) solve();
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值