基环树和笛卡尔树

基环树

就是比平常的树多一条边,有 \(n\) 条边,也就有一个环在里面。

基本思想就是断环,跑树形 \(dp\),或者用拓扑排序判环去跑环形 \(dp\)

树的直径

今天才了解到的,用两遍 \(dfs\) 跑。

首先第一遍找到离根节点最远的节点 \(u_1\)

然后再从 \(u_1\) 找到离它最远的节点 \(u_2\),那么树链 \((u_1,u_2)\) 就是树的直径。

在后面很有用。

#3437. [ZJOI2008]骑士

类似于树形 dp 的思路(没有上司的舞会)。

考虑状态转移方程:

\[dp[u][1]=\sum dp[v][0]+a[u] \]

\[dp[u][0]=\sum \max(dp[v][0],dp[v][1]) \]

其中 \(v\) 表示 \(u\) 的子节点。

然后基环树的思路是先找环,找到环的根之后跑树形 dp。

void dfs(int u)
{
	vis[u]=1;
	dp[u][0]=0,dp[u][1]=a[u];
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v!=root)
		{
			dfs(v);
			dp[u][0]+=max(dp[v][0],dp[v][1]);
			dp[u][1]+=dp[v][0];
		}
		else dp[v][1]=-1145141919;
	}
}
void find(int x)
{
	vis[x]=1;
	root=x;
	while(!vis[fa[root]])
	{
		root=fa[root];
		vis[root]=1;
	}
	dfs(root);
	int tmp=max(dp[root][0],dp[root][1]);
	vis[root]=1;
	root=fa[root];
	dfs(root);
	ans+=max(tmp,max(dp[root][1],dp[root][0]));
}

#P1203. 「NOIP2018」旅行

P3533 [POI2012] RAN-Rendezvous

妈呀,打了一下午成小孩了。

调了一万次甚至动用科技,最后发现树剖炸了、

题目给出一个基环内向树森林,我们首先预处理出每个点所属环上的根节点 \(cir\) 是谁,再对分别对每棵树进行树剖。

然后通过 \(dfs\) 计算每个环的编号 \(id\) 和长度 \(len\)

考虑两个点的状态进行分类讨论:

  • 两点位于不同的基环树中

通过找环的时候给每个环进行编号,查询两点在环上根节点所属环是否一样进行判断;

  • 两点位于同一基环树的同一子树中

意思是两点的 \(cir\) 相同,我们可以直接查询 \(lca(a,b)\) 作为答案;

  • 两点位于同一基环树不同子树中

比较两个 \(cir\) 作为相遇点,哪个更符合要求即可。

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	register int s=0,w=1;
	register char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9')
	{
		s=(s<<1)+(s<<3)+(c^48);c=getchar();
	}return s;
}
int n,m;
const int N=5e5+1;
vector<int>e[N],uu[N];
int indeg[N];
int q[N*2],tt;
int cir[N];
struct HPD{
	int siz[N],dep[N],son[N],fa[N];
	int id[N],tim=0,top[N];
	void dfs1(int u,int fat)
	{
		siz[u]=1,fa[u]=fat;
		if(!indeg[u]) dep[u]=dep[fat]+1;
		cir[u]=cir[fat];
		for(int v:uu[u])
		{
			if(v==fat||indeg[v]) continue;
			dfs1(v,u);
			siz[u]+=siz[v];
			if(siz[v]>siz[son[u]]) son[u]=v;
		}
	}
	void dfs2(int u,int t)
	{
		top[u]=t;
		if(son[u]) dfs2(son[u],t);
		for(int v:uu[u])
		{
			if(v==fa[u]||indeg[v]||v==son[u]) continue;
			dfs2(v,v);
		}
	}
	int lca(int u,int v)
	{
		while(top[u]!=top[v])
		{
			if(dep[top[u]]>=dep[top[v]]) u=fa[top[u]];
			else v=fa[top[v]];
		}
		return dep[u]<dep[v]?u:v;
	}
}hpd;
bool vis[N];
int len[N],pos[N];
int id[N],cur;
void dfs(int u,int le)
{
	len[u]=le,vis[u]=1,pos[u]=le;
	for(int v:e[u])
	{
		if(vis[v]||!indeg[v]) continue;
		id[v]=id[u];
		dfs(v,le+1);
		len[u]=len[v];
	}
}
inline bool cmp(int x1,int y,int x2,int y2)
{
	if(max(x1,y)!=max(x2,y2)) return max(x1,y)<max(x2,y2);
	if(min(x1,y)!=min(x2,y2)) return min(x1,y)<min(x2,y2);
	return x1>=y;
}
int main()
{
	n=read(),m=read();
	register int v;
	for(int i=1;i<=n;i++)
	{
		v=read();
		e[i].push_back(v);
		indeg[v]++;
		uu[v].push_back(i);
	}
	for(int i=1;i<=n;i++)
	{
		if(!indeg[i]) q[++tt]=i;
	}
	register int now;
	while(tt!=0)
	{
		now=q[tt];tt--;
		v=e[now][0];
		indeg[v]--;
		if(!indeg[v]) q[++tt]=v;
	}
	for(int i=1;i<=n;i++)
	{
		if(indeg[i])
		{
			cir[i]=i;
			hpd.dfs1(i,i);
			hpd.dfs2(i,i);
			if(!vis[i]) id[i]=++cur,dfs(i,1);
		} 
	}
	register int a,b;
	while(m--)
	{
		a=read(),b=read();
		if(id[cir[a]]!=id[cir[b]])
		{
			printf("-1 -1\n");continue;
		}
	//	cout<<cir[a]<<" "<<cir[b]<<endl;
		if(cir[a]==cir[b])
		{
			int lc=hpd.lca(a,b);
		//	cout<<lc<<endl;
			printf("%d %d\n",hpd.dep[a]-hpd.dep[lc],hpd.dep[b]-hpd.dep[lc]);
		}
		else
		{
			int cira=pos[cir[a]],cirb=pos[cir[b]];
			int mod=len[cir[a]];
		//	cout<<cira<<" "<<cirb<<endl;
			int t1=hpd.dep[a]+(cirb-cira+mod)%mod;
			int t2=hpd.dep[b]+(cira-cirb+mod)%mod;
			if(cmp(hpd.dep[a],t2,t1,hpd.dep[b])) printf("%d %d\n",hpd.dep[a],t2);
			else printf("%d %d\n",t1,hpd.dep[b]);
		}
	}
}

#3441. 创世纪

奇妙贪心?!

  • 考虑叶子节点:

它本身不会被选,但是它的父亲要被选。

所以直接打上 \(vis\) 然后跳到父亲的父亲。

如果一个基环树的某个环上节点挂了一棵子树,那么这棵树必然会被本次操作全部分解。

  • 考虑环:

上述操作结束之后必然只剩下环(或者不剩)

剩下环的条件是这个基环树本身就是个环(因为这是个基环树森林)

环的贡献是环长除以二 \(len>>1\)

然后就结束了。

posted @ 2024-06-23 14:58  ccjjxx  阅读(8)  评论(0编辑  收藏  举报