仙人掌学习笔记

定义:

  • 是一个连通图。
  • 每一条边最多属于一个简单环。

做法:

DFS树

根据 (tarjan) 的过程,得到一个(DFS)树,同时也可以可得到每个点属于哪一个环(或不属于任何一个环)。对于只属于树的边,进行树形DP;对于环上的边,在环上DP。适用于仅需操作一次的简单仙人掌DP。
代码框架如下(以求仙人掌直径为例):

void dp(int x,int y) {//环上DP
	cnt=0; for(int i=y;i!=x;i=fa[i]) a[++cnt]=f[i]; a[++cnt]=f[x];//处理出环上的点
	for(int i=1;i<=cnt/2;i++)	swap(a[i],a[cnt-i+1]);
	for(int i=cnt+1;i<=cnt+cnt/2;i++) a[i]=a[i-cnt];//复制原串
	q[F=E=1]=1;//单调队列优化DP
	for(int i=2;i<=cnt+cnt/2;i++) {
		if(F<=E&&i-q[F]>cnt/2)	++F;
		ans=Max(ans,a[q[F]]+a[i]+i-q[F]);
		while(F<=E&&a[i]-i>=a[q[E]]-q[E]) --E;
		q[++E]=i;
	}
	for(int i=2;i<=cnt;i++) f[x]=Max(f[x],a[i]+Min(i-1,cnt-i+1));//更新
}

void dfs(int u,int ff) {//模拟tarjan的过程
	dfn[u]=low[u]=++idx,f[u]=0;//f[i]为DP的状态
	for(int i=0;i<e[u].size();i++) {//枚举每条出边
		if(e[u][i]==ff)	continue;//判掉父亲
		if(!dfn[e[u][i]])//未访问过,加边建DFS树
			fa[e[u][i]]=u,dfs(e[u][i],u),low[u]=Min(low[u],low[e[u][i]]);
		else low[u]=Min(low[u],dfn[e[u][i]]);//更新该节点能回到的最早的点
		if(low[e[u][i]]>dfn[u])//进行简单的树形DP
			ans=Max(ans,f[u]+f[e[u][i]]+1),f[u]=Max(f[u],f[e[u][i]]+1);
	}
	for(int i=0;i<e[u].size();i++)//查找仙人掌上的儿子但不是DFS树上的儿子的点,即形成环
		if(e[u][i]!=ff&&fa[e[u][i]]!=u&&dfn[e[u][i]]>dfn[u]) dp(u,e[u][i]);//进行环上DP
}

例题:
SHOI2008 cactus仙人掌图
小C的独立集
HNOI2009 无归岛


圆方树

(听说广义圆方树可以处理一些图的问题,如 【CF Round #278】Tourists, APIO2018 Duathlon 铁人两项)

留坑待填

原文地址:https://www.cnblogs.com/daniel14311531/p/10246731.html