基環樹和笛卡爾樹

ccjjxx發表於2024-06-23

基環樹

就是比平常的樹多一條邊,有 \(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\)

然後就結束了。

相關文章