[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
",max(dp[1][0],dp[1][1]));
}