[BZOJ4316]小C的独立集(仙人掌+树形DP)

[BZOJ4316]小C的独立集(仙人掌+树形DP)

题面

图论王子小C经常虐菜,特别是在图论方面,经常把小D虐得很惨很惨。
这不,小C让小D去求一个无向图的最大独立集,通俗地讲就是:在无向图中选出若干个点,这些点互相没有边连接,并使取出的点尽量多。
小D虽然图论很弱,但是也知道无向图最大独立集是npc,但是小C很仁慈的给了一个很有特点的图: 图中任何一条边属于且仅属于一个简单环,图中没有重边和自环。小C说这样就会比较水了。
小D觉得这个题目很有趣,就交给你了,相信你一定可以解出来的。

分析

考虑对仙人掌Tarjan(但是不必要建圆方树),桥边之间可以直接按树上最大独立集转移(设\(dp_{x,0/1}\)表示\(x\)子树不选/选)。每次出现了环(即DFS的时候搜到边\((x,y)\),出现\(dfn_x<dfn_y\)),就沿着父亲把树上的链提出来,就得到了环上的节点。

再考虑环上如何转移。设底端的点为\(y\),顶端的点为\(x\),讨论这两个点的选择情况。若\(x\)不选,则\(y\)选不选都可以,直接按树的情况转移即可。若\(x\)选,则\(y\)必须不选,此时要把\(dp_{y,1}\)设为\(-\infin\),然后再跑DP转移。

void calc(int x,int y){//x到y的非树边构成一个环 
	int f0=0,f1=0;//环上下一个点不选/选的转移 
	//此时x不选 
	for(int i=y;i!=x;i=fa[i]){
		int t0=f0+dp[i][0];
		int t1=f1+dp[i][1];
		f0=max(t0,t1);//下一个点不选,所以i选不选都没关系
		f1=t0;//下一个点选 
	}
	dp[x][0]+=f0;
	//此时x选
	f0=0,f1=-INF;//-INF保证y一定不选,因为(x,y)有边
	for(int i=y;i!=x;i=fa[i]){
		int t0=f0+dp[i][0];
		int t1=f1+dp[i][1];
		f0=max(t0,t1);//下一个点不选,所以i选不选都没关系
		f1=t0;//下一个点选 
	}
	dp[x][1]+=f1;
}

代码

#include<iostream>
#include<cstdio>
#include<cstring> 
#define maxn 50000
#define maxm 60000
#define INF 0x3f3f3f3f
using namespace std;
struct edge{
	int from;
	int to;
	int next;
}E[maxm*2+5];
int head[maxn+5];
int esz=1;
void add_edge(int u,int v){
	esz++;
	E[esz].from=u;
	E[esz].to=v;
	E[esz].next=head[u];
	head[u]=esz;
} 
int fa[maxn+5];
int dp[maxn+5][2];
void calc(int x,int y){//x到y的非树边构成一个环 
	int f0=0,f1=0;//环上下一个点不选/选的转移 
	//此时x不选 
	for(int i=y;i!=x;i=fa[i]){
		int t0=f0+dp[i][0];
		int t1=f1+dp[i][1];
		f0=max(t0,t1);//下一个点不选,所以i选不选都没关系
		f1=t0;//下一个点选 
	}
	dp[x][0]+=f0;
	//此时x选
	f0=0,f1=-INF;//-INF保证y一定不选,因为(x,y)有边
	for(int i=y;i!=x;i=fa[i]){
		int t0=f0+dp[i][0];
		int t1=f1+dp[i][1];
		f0=max(t0,t1);//下一个点不选,所以i选不选都没关系
		f1=t0;//下一个点选 
	}
	dp[x][1]+=f1;
}
int tim,dfn[maxn+5],low[maxn+5];
void tarjan(int x,int last_edge){
	dfn[x]=low[x]=++tim;
	dp[x][0]=0;dp[x][1]=1;
	for(int i=head[x];i;i=E[i].next){
		int y=E[i].to;
		if(!dfn[y]){
			fa[y]=x;
			tarjan(y,i);
			low[x]=min(low[x],low[y]);
		}else if(i!=(last_edge^1)) low[x]=min(low[x],dfn[y]);
		if(dfn[x]<low[y]){//桥边直接转移 
			dp[x][0]+=max(dp[y][0],dp[y][1]);
			dp[x][1]+=dp[y][0];
		}
	}
	for(int i=head[x];i;i=E[i].next){
		int y=E[i].to;
		if(x!=fa[y]&&dfn[x]<dfn[y]) calc(x,y);//出现了环 
	}
}

int n,m;
int main(){
	int u,v;
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;i++){
		scanf("%d %d",&u,&v);
		add_edge(u,v);
		add_edge(v,u);
	}
	tarjan(1,0);
	printf("%d\n",max(dp[1][0],dp[1][1]));
}
posted @ 2020-12-01 16:06  birchtree  阅读(122)  评论(0编辑  收藏  举报