【BZOJ1487】[HNOI2009]无归岛(动态规划)
题面
题解
哪来的这么多废话啊,直接说一个仙人掌得了。
然后就是要你求仙人掌最大独立集了。(随便蒯份原来的代码就过了)
不过我还是重新整理一遍思路吧。
一种是裸的(dp),只需要额外考虑上环的影响就好了。
这种方法我们从树上的做法推广过来。
先考虑树的最大独立集,设(f[i][0/1])表示当前考虑(i)及其子树,这个点一定不选,以及随意的最大独立集。转移的时候枚举这个点选还是不选即可。
推广到仙人掌上,相比于树,还多出来一条返祖边。所以额外维护一维(0/1),表示这条边所在的环的最底下的那个端点是否被选。分情况讨论转移即可,详细的解法戳这里。
另外一种方法基于圆方树的思想。我们假装构建出来了圆方树(事实上不会构建出来的,只是利用了这个形式)。对于环而言,我们可以单独把环扣下来,显然除了环之外挂的子树对于这个环上的点是否选择是无关的。状态和树上的(dp)一样的设计。转移的时候这样子:如果这条边是一条树边,那么我们直接转移就好了,就把他当成一棵树来做。否则是一条返祖边,那么我们单独考虑这个环的贡献。现在的形式就是环上挂着一串的子树,子树内的答案我们已知,那么从一个方向开始遍历整个环,记录一下这个点选还是没有选的最大贡献,显然最终这个环的贡献只需要合并到深度最浅的那个点上面就可以了。那么枚举这个深度最浅的点是选还是不选,直接贪心抉择一下即可。更加具体的戳这里
代码一:直接(dp)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define RG register
#define MAX 100100
inline int read()
{
RG int x=0,t=1;RG char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=-1,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return x*t;
}
struct Line{int v,next;}e[MAX*3];
int h[MAX],cnt=1,n,m;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int dep[MAX],fa[MAX],a[MAX];
int tp[MAX],un[MAX];
void dfs(int u,int ff)
{
fa[u]=ff;dep[u]=dep[ff]+1;
for(int i=h[u];i;i=e[i].next)
if(!dep[e[i].v])dfs(e[i].v,u);
}
void jump(int u,int v){int x=v;while(x!=u)tp[x]=u,un[x]=v,x=fa[x];}
int f0[MAX],f1[MAX],g0[MAX],g1[MAX];
void dp(int u)
{
f1[u]=a[u];
if(u!=un[u])g1[u]=a[u];
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;if(dep[u]+1!=dep[v])continue;
dp(v);
if(un[u]!=un[v])g0[u]+=f1[v],g1[u]+=g0[v];
else g0[u]+=g1[v],g1[u]+=g0[v];
if(tp[v]!=u)f0[u]+=f1[v],f1[u]+=f0[v];
else f0[u]+=f1[v],f1[u]+=g0[v];
}
f1[u]=max(f1[u],f0[u]);
g1[u]=max(g1[u],g0[u]);
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
}
for(int i=1;i<=n;++i)a[i]=read();
dfs(1,0);
for(int u=1;u<=n;++u)
for(int i=h[u];i;i=e[i].next)
if(dep[u]<dep[e[i].v]&&fa[e[i].v]!=u)
jump(u,e[i].v);
dp(1);
printf("%d
",f1[1]);
return 0;
}
类似圆方树的思想的做法:
#include<iostream>
#include<cstdio>
using namespace std;
#define MAX 100100
inline int read()
{
int x=0;bool t=false;char ch=getchar();
while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
if(ch=='-')t=true,ch=getchar();
while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
return t?-x:x;
}
struct Line{int v,next;}e[MAX<<2];
int h[MAX],cnt=1;
inline void Add(int u,int v){e[cnt]=(Line){v,h[u]};h[u]=cnt++;}
int n,m,a[MAX];
int dfn[MAX],low[MAX],tim,f[MAX][2],fa[MAX];
void dp(int u,int y)
{
int f0=0,f1=0,t0,t1;
for(int i=y;i!=u;i=fa[i])
{
t0=f0+f[i][0];t1=f1+f[i][1];
f0=max(t0,t1);f1=t0;
}
f[u][0]+=f0;
f0=0;f1=-1e9;
for(int i=y;i!=u;i=fa[i])
{
t0=f0+f[i][0];t1=f1+f[i][1];
f0=max(t0,t1);f1=t0;
}
f[u][1]+=f1;
}
void Tarjan(int u,int ff)
{
dfn[u]=low[u]=++tim;fa[u]=ff;f[u][1]=a[u];
for(int i=h[u];i;i=e[i].next)
{
int v=e[i].v;
if(!dfn[v])Tarjan(v,u),low[u]=min(low[u],low[v]);
else if(v!=ff)low[u]=min(low[u],dfn[v]);
if(low[v]>dfn[u])
f[u][0]+=max(f[v][0],f[v][1]),f[u][1]+=f[v][0];
}
for(int i=h[u];i;i=e[i].next)
if(fa[e[i].v]!=u&&dfn[u]<dfn[e[i].v])
dp(u,e[i].v);
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read();
Add(u,v);Add(v,u);
}
for(int i=1;i<=n;++i)a[i]=read();
Tarjan(1,0);
printf("%d
",max(f[1][0],f[1][1]));
return 0;
}