[BZOJ4316]小C的独立集-圆方树-动态规划

小C的独立集

Description

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

Input

第一行,两个数n, m,表示图的点数和边数。
第二~m+1行,每行两个数x,y,表示x与y之间有一条无向边。

Output

输出这个图的最大独立集。

Sample Input

5 6
1 2
2 3
3 1
3 4
4 5
3 5

Sample Output

2

HINT

100% n <=50000, m<=60000


新姿势圆方树get
再也不会在仙人掌题面前一脸懵逼了~
只是…….
说好的这东西好写好调呢咱可是WA了5次啊……

(╯‵□′)╯︵┻━┻


思路:
先扯一波圆方树:
仙人掌这种东西,正如题面所说,仙人掌中任何一条边属于且仅属于一个简单环。
也就是说,环不会边相交。
那么考虑处理环的通用做法:用tarjan缩环。
对于仙人掌,因为它是树形结构,所以咱需要在缩环的同时,保留原本在树上的所有信息。
定义原本就在仙人掌上的点为圆点。
对于每个环,咱新建一个点,来代表这个环。称这样的点为方点。
由于咱仍需要环上的点的信息,所以不能将它们删去。
同时,为了让圆方树最后是一棵树,考虑这样表示一个环:
令一个环上离根节点最近的节点为这个环的根节点。
作为环的代表,方点的父亲节点设为环的根节点的父亲。
对于每个环上的圆点,保留其不属于环的边,其中环的根节点不保留连向父亲的边。
每个环上的圆点向所属环的方点连接一条边,以方点为其父亲。
如果环存在边权,则圆点连向方点的边权设为该点到环的根节点的最短路径,方点连向父亲的边权设为0。
这样再给方点打上标记,圆方树就建好了!

然后是此题思路:
独立集的话,如果是树那就好做多了。
如果是仙人掌嘛……只需要断掉某条边,强制不选这条边其中一个点,跑两树形树形dp,就可以了~

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

const int M=120009;
const int Inf=2147483647;

int n,m,nn;
int pre[M],fa[M],dfn;
bool ring[M];
int to[M],nxt[M],beg[M],tot;
int eto[M],enxt[M],ebeg[M],etot=1;
int f[M][3],stk[M][3],top;

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
}

inline void adde(int u,int v)
{
    eto[++etot]=v;
    enxt[etot]=ebeg[u];
    ebeg[u]=etot;
}

void dfs(int u,int fae)
{
    pre[u]=++dfn;
    for(int i=ebeg[u],v;i;i=enxt[i])
        if((i^1)!=fae)
        {
            if(!pre[v=eto[i]])
            {
                fa[v]=u;
                ring[u]=0;
                dfs(v,i);
                if(!ring[u])
                {
                    add(u,v);
                    add(v,u);
                }
            }
            else if(pre[v]<=pre[u])
            {
                int tmp=u;
                n++;
                while(1)
                {
                    add(n,tmp);
                    add(tmp,n);
                    ring[tmp]=1;
                    if(tmp==v)
                        break;
                    tmp=fa[tmp];
                }
            }
        }
}

void dp(int u,int faa)
{
    if(u<=nn)
    {
        f[u][0]=0;
        f[u][1]=1;
        for(int i=beg[u],v;i;i=nxt[i])
            if((v=to[i])!=faa)
            {
                dp(v,u);
                if(v<=nn)
                {
                    f[u][0]+=max(f[v][0],f[v][1]);
                    f[u][1]+=f[v][0];
                }
            }
    }
    else
    {
        for(int i=beg[u],v;i;i=nxt[i])
            if((v=to[i])!=faa)
                dp(v,u);

        top=0;
        for(int i=beg[u];i;i=nxt[i])
        {
            stk[++top][0]=f[to[i]][0];
            stk[top][1]=f[to[i]][1];
        }

        for(int i=top-1;i;i--)
        {
            stk[i][0]+=max(stk[i+1][1],stk[i+1][0]);
            stk[i][1]+=stk[i+1][0];
        }

        f[faa][0]=stk[1][0];

        for(int i=1;i<top;i++)
        {
            stk[i][0]-=max(stk[i+1][1],stk[i+1][0]);
            stk[i][1]-=stk[i+1][0];
        }

        stk[top][1]=0xefefefef;
        for(int i=top-1;i;i--)
        {
            stk[i][0]+=max(stk[i+1][1],stk[i+1][0]);
            stk[i][1]+=stk[i+1][0];
        }

        f[faa][1]=stk[1][1];
    }
}

int main()
{
    n=read();
    m=read();

    for(int i=1,u,v;i<=m;i++)
    {
        u=read();
        v=read();
        adde(u,v);
        adde(v,u);
    }

    nn=n;
    dfs(1,0);
    dp(1,0);
    printf("%d\n",max(f[1][0],f[1][1]));
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值