定义:
- 是一个连通图。
- 每一条边最多属于一个简单环。
做法:
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 铁人两项)
留坑待填