P4410 [HNOI2009] 无归岛 题解

题意就是特殊的仙人掌(注:大环中每个点(岛)都是单点或若干三元环的组合)上最大独立集。

我们就不提拆成三元环和大环处理的方法了(您可以看 lupengheyyds 的题解),直接考虑普通仙人掌上最大权独立集问题。

考虑暴力计算最大权独立集的过程:

  • 暴力考虑某点是否选择

  • 判断解的合法性

  • 更新答案

那么我们需要的关键信息,用于刻画问题的就是:

  • 一个点是否被选择

  • 相邻点的选择情况

考虑一个简化的问题,如果在树上求解呢?那么这两条信息就是:

  • 一个点是否选择

  • 子节点的选择情况(or 父节点的选择情况)

这就是 P1352 没有上司的舞会

考虑仙人掌上的问题,发现第二条信息不好处理,因为节点间的关系复杂了

但是我们发现仙人掌拥有如下的性质:

  • 它有环,但是没有一个边同时在 22 个环里,如果从树的角度来看,仙人掌就是树加上边,最后满足同一个点 至多11 条返祖/后向边

也就是说仙人掌虽然连边复杂了,但是对于每一个点来说,它可能同时连接在很多链/很多简单环中,但是一个环中能影响它的选择情况的,只有 22 个点,而一条链中能影响它的选择情况的,只有 11 个点。

所以,非常暴力的思想就是用 DFS 树来确定更新顺序,令 fx,0/1,0/1f_{x,0/1,0/1} 表示当前 xx 这个点选/不选,且这个点所在的环(这个点不为环顶的环)的环底选/不选时的最值(环顶/底:一个环中 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 归约而来。

这就做完了。

posted @ 2024-01-10 17:15  haozexu  阅读(11)  评论(0)    收藏  举报  来源