CF 1529E. Trees of Tranquillity

CF 1529E. Trees of Tranquillity

题意:

有A1,A2两棵树,根是1,编号都是1~n,先制作图A3,如果两个点的x和y同时满足以下两个条件则连边,
1.在树A中x是y的祖先或者y是x的祖先
2.在树B中x和y谁都不是谁的祖先
求A3的最大的团集的大小
团:图G的一个完全子图
题目A1和A2的输入方式为:
a2,a3…an, ai是树的顶点i的父亲节点(1<= ai <i)

题解:

参考题解:
文章1
文章2
我们需要将题意转化:
满足最大团的点是什么样的?团要求任意两点都有连线,也就是最大团中所有点同时满足题目说的两个条件,因此这些点在A树上是一条的(没有分支)。在B树上体现为彼此不是父亲节点,我们引入dfs序,发现所有点的dfs序没有交集。
对于本题的输入还有一个特殊性质:ai <i,说明A树和B树从根节点出发的链,一定是一个单调递增的序列。针对B树的dfs序区间,就会有:序号较小的点的区间,要么包含序号较大的点的区间,要么与其不相交
对于两个点的dfs序区间,要么没有交集(不是父子关系),要么存在包含(父子关系),且左端点与右端点是对应的,父亲系节点的区间包含儿子节点的区间,因此对于一个互相包含的区间,我们要保留较小的(这样才能存下更多不相交的区间),对应到树上,相当于 当一个点的儿孙可选时该点不选最优
现在我们从第一棵树的根开始dfs,每到一个点就往某数据结构中加入自己的区间:
1.如果被数据结构中原先的更大区间包含,那就删除大区间,加入小区间
2.如果包含了原有的至少一个小区间,那就不加
3.否则就加进去,答案+1
回溯时,数据结构要退回原来的状态

这个某数据结构可以用set,线段树等等
线段树具体实现过程:
先求树2的dfs序,然后对树1从根节点开始,查看当前点u的区间内是否有区间,如果没有直接插入,如果有,一定是比自己大的区间,所以删除原大区间,加入新区间。记录当前答案最大值,对u的儿子继续查找。回溯时逆向操作

代码:

线段树代码:

代码里有注释

#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
#include<list>
#include<unordered_map>
#define lowbit(x) x&-x
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
template<typename T>
inline void read(T &x)
{
	T f=1;x=0;
	char ch=getchar();
	while(0==isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(0!=isdigit(ch)) x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
	x*=f;
}
template<typename T>
inline void write(T x)
{
	if(x<0){x=~(x-1);putchar('-');}
    if(x>9)write(x/10);
    putchar(x%10+'0');
}
const int inf=0x3f3f3f3f;
const int N=1e6+100;
vector<int>a[N],b[N];
int L[N],R[N],dfn,sum,ans;
struct Node {
	int l,r,mmax,lazy;
}tree[N<<2];
void pushup(int k) {
	tree[k].mmax=max(tree[k<<1].mmax,tree[k<<1|1].mmax);
}
void pushdown(int k) {
	if(tree[k].lazy!=-1) {
		int lz=tree[k].lazy;
		tree[k].lazy=-1;
		tree[k<<1].mmax=tree[k<<1|1].mmax=lz;
		tree[k<<1].lazy=tree[k<<1|1].lazy=lz;
	}
}
void build(int k,int l,int r) {
	tree[k]={l,r,0,-1};
	if(l==r) {
		return;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
}
void update(int k,int l,int r,int val) {
	if(tree[k].l>r||tree[k].r<l) {
		return;
	}
	if(tree[k].l>=l&&tree[k].r<=r) {
		tree[k].mmax=tree[k].lazy=val;
		return;
	}
	pushdown(k);
	update(k<<1,l,r,val);
	update(k<<1|1,l,r,val);
	pushup(k);
}
int query(int k,int l,int r) {
	if(tree[k].l>r||tree[k].r<l) {
		return 0;
	}
	if(tree[k].l>=l&&tree[k].r<=r) {
		return tree[k].mmax;
	}
	pushdown(k);
	return max(query(k<<1,l,r),query(k<<1|1,l,r));
}
void dfs1(int u) {//求树2的dfs序 
	L[u]=++dfn;
	for(auto v:b[u]) {
		dfs1(v);
	}
	R[u]=dfn;
}
void dfs2(int u) {
	int mmax=query(1,L[u],R[u]);
	if(!mmax) {//如果没有区间 
		update(1,L[u],R[u],u);
		sum++;
	} else {//存在区间,且区间一定比自己大 
		update(1,L[mmax],R[mmax],0);//删除原本区间 
		update(1,L[u],R[u],u);//加入新区间 
	}
	ans=max(ans,sum);
	for(auto v:a[u]) {
		dfs2(v);//对u的儿子节点继续查找 
	}
	//回溯操作 
	if(!mmax) {
		update(1,L[u],R[u],0);
		sum--;
	} else {
		update(1,L[u],R[u],0);
		update(1,L[mmax],R[mmax],mmax);
	}
}
int main()
{
	int w;
	cin>>w;
	while(w--) {
		int n;
		read(n);
		dfn=0;
		for(int i=1;i<=n;i++) {
			a[i].clear();
			b[i].clear();
		}
		for(int i=2;i<=n;i++) {
			int fa;
			read(fa);
			a[fa].push_back(i);
		}
		for(int i=2;i<=n;i++) {
			int fa;
			read(fa);
			b[fa].push_back(i);
		}
		dfn=ans=sum=0;
		build(1,1,n);
		dfs1(1);
		dfs2(1);
		cout<<ans<<endl;
	}
	return 0;
}

利用set实现

#include<set>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 300005
#define ENDL putchar('\n')
#define LL long long
#define DB double
#define lowbit(x) ((-x) & (x))
#define SI set<int>::iterator
LL read() {
	LL f = 1,x = 0;char s = getchar();
	while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}
	while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}
	return f * x;
}
int n,m,i,j,s,o,k;
vector<int> g0[MAXN];
int L[MAXN],R[MAXN],lR[MAXN],tim;//lR[]数组是为L找到唯一的R 
void dfs0(int x,int ff) {//求 树2的dfs序 
	L[x] = ++ tim;
	for(int i = 0;i < (int)g0[x].size();i ++) {
		if(g0[x][i] != ff) 
			dfs0(g0[x][i],x);
	}
	R[x] = tim; 
	lR[L[x]] = R[x];
	return ;
}
vector<int> g[MAXN];
int d[MAXN],dfn[MAXN],rr[MAXN],cnt,ans;
set<int> st;
void dfs(int x,int ff) {
	int ad = 0;
	if(st.empty()) st.insert(L[x]);//如果此时为空,直接插入 
	else {
		SI i = st.lower_bound(L[x]);//查找是否已经有区间 
		
		if(i != st.begin()) //发现有区间 
		{
			i --;
			if(lR[*i] >= R[x])//存在更大区间包含 
			{
				ad = *i;
				st.erase(ad);//删除大区间 
				st.insert(L[x]);//加入小区间 
			}
			else 
			{
				i ++;
				if(i == st.end() || *i > R[x]) //里面没有小区间,加入的区间不会相交 
					st.insert(L[x]);
			}
		}
		else if(i == st.end() || *i > R[x]) 
			st.insert(L[x]);//发现没区间 
	}
	ans = max(ans,(int)st.size());
	for(int i = 0;i < (int)g[x].size();i ++) {
		if(g[x][i] != ff) 
			dfs(g[x][i],x);
	}
	
	//回溯操作 
	if(st.find(L[x]) != st.end()) st.erase(L[x]);
	if(ad) st.insert(ad);
	return ;
}
int main() {
	int T = read();
	while(T --) {
		n = read();
		tim = 0; cnt = 0;
		st.clear();
		for(int i = 1;i <= n;i ++) {
			g0[i].clear();
			g[i].clear();
			lR[i] = 0;
		}
		for(int i = 2;i <= n;i ++) {
			s = read(); 
			g[s].push_back(i);
		}
		for(int i = 2;i <= n;i ++) {
			s = read(); 
			g0[s].push_back(i);
		}
		dfs0(1,0);
		ans = 0;
		dfs(1,0);
		printf("%d\n",ans);
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值