【BZOJ4316】小C的独立集(动态规划)

题面

BZOJ

题解

考虑树的独立集求法
f[i][0/1] f [ i ] [ 0 / 1 ] 表示 i i 这个点一定不选,以及i这个点无所谓的最大值
转移 f[u][0]=f[v][1] f [ u ] [ 0 ] = ∑ f [ v ] [ 1 ] f[u][1]=f[v][0] f [ u ] [ 1 ] = ∑ f [ v ] [ 0 ] , f[u][1]=max(f[u][1],f[u][0]) f [ u ] [ 1 ] = m a x ( f [ u ] [ 1 ] , f [ u ] [ 0 ] )
现在放在了仙人掌上,
我们可以看做一棵树加上了若干不相交的返祖边
于是再加上一维 f[u][0/1][0/1] f [ u ] [ 0 / 1 ] [ 0 / 1 ]
其中最后一维表示这条边所在的环的最底端的那个点一定不选,或者无所谓
赋初值: f[u][1][1]=1 f [ u ] [ 1 ] [ 1 ] = 1 ,如果这个点不是所在环的最底端, f[u][1][0]=1 f [ u ] [ 1 ] [ 0 ] = 1
此时的转移:
1.两个点的底端点相同
这个时候我们先只考虑强制不选底端的转移
那么, f[u][1][0]+=f[v][1][1],f[u][1][1]+=f[v][1][0] f [ u ] [ 1 ] [ 0 ] + = f [ v ] [ 1 ] [ 1 ] , f [ u ] [ 1 ] [ 1 ] + = f [ v ] [ 1 ] [ 0 ]
也就是上面裸的在树上的转移

2.两个点的底端点不同
既然跨越了环,意味着 u u 就是这个环的底端点,v是它所在环的顶端点
那么,可以 u u v不选,因为跨越了环,所以对于 v v 的底端点选择与否我们是不关心的
而第二维的1表示的 u u 无所谓,后面的0则是强制不选择 u u
因此f[u][0][0]+=f[v][1][1] f[u][1][0]+=f[v][0][0] f [ u ] [ 1 ] [ 0 ] + = f [ v ] [ 0 ] [ 0 ]

3. v v 的顶端点不是u
意味着不用担心底端点产生的影响
所以 f[u][0][1]+=f[v][1][1] f [ u ] [ 0 ] [ 1 ] + = f [ v ] [ 1 ] [ 1 ] f[u][1][1]+=f[v][0][1] f [ u ] [ 1 ] [ 1 ] + = f [ v ] [ 0 ] [ 1 ]

4. v v 的顶端点是u
此时要考虑底端点的贡献了
此时当前 u u 不选,那就没有什么问题f[u][0][1]+=f[v][1][1]
当前 u u 选择,强制不能选择底端点f[u][1][1]+=f[v][0][0]

好了,这样就讨论完了四种转移,然后就可以啦

#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 55555
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];
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]=1;
    if(u!=un[u])g1[u]=1;
    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);
    }
    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\n",f1[1]);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值