P4410 [HNOI2009] 无归岛 题解
题意就是特殊的仙人掌(注:大环中每个点(岛)都是单点或若干三元环的组合)上最大独立集。
我们就不提拆成三元环和大环处理的方法了(您可以看 lupengheyyds 的题解),直接考虑普通仙人掌上最大权独立集问题。
考虑暴力计算最大权独立集的过程:
-
暴力考虑某点是否选择
-
判断解的合法性
-
更新答案
那么我们需要的关键信息,用于刻画问题的就是:
-
一个点是否被选择
-
相邻点的选择情况
考虑一个简化的问题,如果在树上求解呢?那么这两条信息就是:
-
一个点是否选择
-
子节点的选择情况(or 父节点的选择情况)
这就是 P1352 没有上司的舞会
考虑仙人掌上的问题,发现第二条信息不好处理,因为节点间的关系复杂了
但是我们发现仙人掌拥有如下的性质:
- 它有环,但是没有一个边同时在 个环里,如果从树的角度来看,仙人掌就是树加上边,最后满足同一个点 至多 有 条返祖/后向边
也就是说仙人掌虽然连边复杂了,但是对于每一个点来说,它可能同时连接在很多链/很多简单环中,但是一个环中能影响它的选择情况的,只有 个点,而一条链中能影响它的选择情况的,只有 个点。
所以,非常暴力的思想就是用 DFS 树来确定更新顺序,令 表示当前 这个点选/不选,且这个点所在的环(这个点不为环顶的环)的环底选/不选时的最值(环顶/底:一个环中 DFS 序最小的叫环顶,最大的叫环底)
转移方程会比较长,请看代码注释:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=2*N;
int n,m,val[N],f[N][2][2];
// 当前节点/是否选择当前节点/是否选择环底
int tim,dfn[N];
vector<int> mp[N];
int dp(int x,int fa){
dfn[x]=++tim;
f[x][1][0]=f[x][1][1]=val[x];
int rt=0;
for(int v:mp[x]){
if(fa==v) continue;
if(!dfn[v]){
int root=dp(v,x);
//当自己不是环顶时的返回值是自己所在的大环的环顶
if(root!=dfn[x])rt=root;
//如果当前节点的dfn等于返回值,则当前正在遍历一个环的第一条边
f[x][1][0]+=f[v][0][0];
//不论当前是否为环顶,都应该这样更新
f[x][1][1]+=f[v][0][(root==dfn[x])?0:1];
//如果是环顶, 那就必须不选环底,否则照常更新
if(root==dfn[x]){
//只要不选环顶,就对子节点的状态没有任何限制
f[x][0][0]+=max(max(f[v][0][1],f[v][0][0]),max(f[v][1][0],f[v][1][1]));
f[x][0][1]+=max(max(f[v][0][1],f[v][0][0]),max(f[v][1][0],f[v][1][1]));
}
else{
//不是环顶,照常更新
f[x][0][0]+=max(f[v][0][0],f[v][1][0]);
f[x][0][1]+=max(f[v][0][1],f[v][1][1]);
}
}else if(dfn[x]>dfn[v]){
rt=dfn[v];//如果找到返祖边
//那么这个点就是环底,依照定义,以下情况不合法
f[x][1][0]=-0x3f3f3f3f;
f[x][0][1]=-0x3f3f3f3f;
}
}
return rt;//小技巧:递归地返回所在的的环顶
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;cin>>u>>v;
mp[u].push_back(v),mp[v].push_back(u);
}
for(int i=1;i<=n;i++) cin>>val[i];
dp(1,0);
cout<<max(max(f[1][0][1],f[1][0][0]),max(f[1][1][1],f[1][1][0]));
return 0;
}
关于使用圆方树解决的题解(点击查看原题解),在此我想对那篇题解做一个注解,解释一下方点的转移方法:
那篇题解是这样处理方点的:
int f0=0, f1=-1.4e8, g0=-1.4e8, g1=0;
//f是代表父节点(即环顶)不选的情况,g是代表要选的情况
for(int i=hh[u]; i; i=nt[i]){
int v=to[i]; if(v==fa)continue;
dp(v, u);
int o0=f0, o1=f1;
f0=max(o0, o1)+f[v][0], f1=o0+f[v][1];
//不选的时候,由于圆方树加边的特性,上一个扫描的儿子和当前扫描的儿子
//在原图上面是相邻的,于是可以更新
o0=g0, o1=g1;
g0=max(o0, o1)+f[v][0], g1=o0+f[v][1];
//要选环顶的时候,由于 g0 初值是inf,所以第一条直接与环顶接壤的边不会被更新到
}
f[u][0]=max(f0, f1), f[u][1]=g0;
//这里没有用 g1 更新,因为最后一个遍历的儿子必然是环底,这样做保证了不选环底
(我才不会告诉你我看了 2days 还不懂问了机房大佬 way 才懂的)
对于更扩展的问题,无向图上的最大独立集问题,很遗憾,这是一个 NP-Complete 问题,可以从 3-SAT 归约而来。
这就做完了。
本文来自博客园,作者:haozexu,转载请注明原文链接:https://www.cnblogs.com/haozexu/p/18281750

浙公网安备 33010602011771号