PQ树及其例题

本文介绍了PQ树(优先队列树)的概念,包括其结构和构建过程。PQ树用于解决一类特殊的树形结构问题,其中节点分为P类和Q类,具有特定的排列约束。文章详细阐述了如何通过DFS进行PQ树的构建,并提供了C++代码实现。在给定一组关键点的情况下,判断是否能构建满足条件的PQ树,并计算其排列组合数。
摘要由CSDN通过智能技术生成

PQ树

参考1
参考2

PQ树是什么?

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

PQ树构建

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define re(a) scanf("%d",&a)
#define r(i,a,b) for(int i=a;i<=b;i++)
#define rr(i,a,b) for(int i=a;i>=b;i--)
#define in inline
#define me(a) memset(a,0,sizeof(a))
#define mem(a) memset(a,0x3f,sizeof(a))
#define ll long long
#define db double
using namespace std;
const int N=1e3+7;
const int p=998244353;
int n,fac[N],m,q;
struct node{
	vector<int> son;
	int op,type;
	/*
	op:1 无关键点 
		2 全是关键点 
		3 一部分是关键点
		
	type: 0 p类点(内部儿子可以随便排列)
		  1 q类点(内部儿子必须按照正序,或者按照逆序排列)	 
	*/
}t[N<<2];
int cnt=0,rt;
bool ok,vis[N];
in void init(){
	cnt=n,rt=++cnt;
	r(i,1,n)t[rt].son.push_back(i);
}
in void dfs1(int u){
	if(u<=n){
		t[u].op=vis[u]?2:1;
		return;
	}
	t[u].op=0;
	int len=t[u].son.size()-1;
	r(i,0,len){
		dfs1(t[u].son[i]);
		t[u].op|=t[t[u].son[i]].op;
	}
}
in void dfs2(int u,int lim){
	/*
	lim 0:内部关键点排列无所谓
	lim 1:内部关键点全部排列在左
	lim 2:内部关键点全部排列在右 
	*/
	if(!ok||t[u].op!=3)return;
	vector<int> a[4];
	/*
	记录u的 不同op的儿子的集合 
	*/
	int len=t[u].son.size()-1;
	r(i,0,len){
		int v=t[u].son[i];
		a[t[v].op].push_back(v);
	}
	if((lim>0)+a[3].size()>=3){
		/*
		a[3].size()<=2,(有lim时a[3].size()<=1)有解 
		 其他情况无解 
		*/
		ok=0;
		return;
	}
	if(!lim&&(a[2].size()+a[3].size())==1){
		//关键点全在一个子树上 
		if(!a[2].empty())dfs2(a[2][0],lim);
		if(!a[3].empty())dfs2(a[3][0],lim);
		return;
	}
	
	if(t[u].type){
		// q,内部结点有顺序,只需要考虑从左到右枚举 
		int now=0;
		/*
		now记录当前状态
		0:还从没有关键点出现过(关键点只出现在op为2,3的子树内)
		1:上一个位置(若当前是i,则上一个位置是i-1)有关键点
		2:上一个位置没关键点,但关键点出现过 
		*/
		vector<int> S;
		if(t[t[u].son[0]].op==2||t[t[u].son.back()].op==1){
			//先默认lim=2,将关键点向后排列 
			reverse(t[u].son.begin(),t[u].son.end());
		}
		int len=t[u].son.size()-1;
		r(i,0,len){
			int v=t[u].son[i];
			if(t[v].op==1){
				S.push_back(v);
				now+=now==1;
			}
			else if(t[v].op==2){
				S.push_back(v);
				now+=!now;
				if(now==2)ok=0;
			}else{
				if(now==2)ok=0;
				++now;
				dfs2(v,3-now);
				S.insert(S.end(),t[v].son.begin(),t[v].son.end());
			}
		}
		if(lim&&now==2)ok=0;
		if(lim==1)reverse(S.begin(),S.end());
		t[u].son=S;
	}else{
		int z=-1;
		if(a[2].size()==1)z=a[2][0];
		else if(a[2].size()>1)z=++cnt,t[z].type=0,t[z].son=a[2];
		vector<int> S;//用来存储除了a[1]以外的点 
		if(!a[3].empty()){
			dfs2(a[3][0],2);
			S.insert(S.end(),t[a[3][0]].son.begin(),t[a[3][0]].son.end());
		}
		if(~z)S.push_back(z);
		if(a[3].size()>1){
			dfs2(a[3][1],1);
			S.insert(S.end(),t[a[3][1]].son.begin(),t[a[3][1]].son.end());
		}
		if(a[1].empty()){
			if(lim==1)reverse(S.begin(),S.end());
			t[u].type=1;t[u].son=S;
		}else{
			if(lim){
				t[u].son.clear();
				t[u].type=1;
				z=a[1][0];
				if(a[1].size()>1)z=++cnt,t[z].son=a[1],t[z].type=0;
				t[u].son.push_back(z);
				t[u].son.insert(t[u].son.end(),S.begin(),S.end());
				if(lim==1)reverse(t[u].son.begin(),t[u].son.end());
			}else{
				z=S[0];
				if(S.size()>1)z=++cnt,t[z].type=1,t[z].son=S;
				t[u].son=a[1],t[u].son.push_back(z);
			}
		}
	}
	
}
in bool Insert(){
	dfs1(rt);ok=1;
	dfs2(rt,0);
	return ok;
}
in int cal(int u){
	if(u<=n)return 1;
	int ans=t[u].type?2:fac[t[u].son.size()];
	int len=t[u].son.size()-1;
	r(i,0,len){
		int v=t[u].son[i];
		ans=1ll*ans*cal(v)%p;
	}
	return ans;
}
int main(){
	re(n),re(m);fac[0]=1;int x;
	r(i,1,n)fac[i]=1ll*fac[i-1]*i%p;
	init();
	while(m--){
		re(q);me(vis);
		r(i,1,q)re(x),vis[x]=1;
		if(!Insert()){
			printf("0\n");
			return 0;
		}
	}
	printf("%d\n",cal(rt));
	return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值