UVALIVE 3523(双连通分量+二分图染色)

题目链接:UVALIVE 3523

解题思路:
这题是一道神题,考察的内容非常综合!
这题最终转化为求解图中结点是在一个奇圈上。首先我们可以把所有的圈找出来,即找到所有的双连通分量,跑一边tarjan算法即可。之后重头戏来了,我们获得一个双连通块之后,怎么判断块中的点是不是在一个奇圈上?答案——二分图染色!
定理:一个图为二分图的充分必要条件是图中不存在奇圈。
因此,如果一个双连通块为二分图,则不存在奇圈;如果一个双连通块不是二分图,则一定存在奇圈。但是这样还有一个问题,我们能否保证所有非二分图双连通块的结点都在一个奇圈上?答案也是可以的。
可以这么想,因为块中一定存在一个奇圈,又因为块双连通,即任意结点u到结点v必存在两条点不重复路径,那么假设v在奇圈上,我们考虑u到v的两条路径:若u也在一个奇圈上,则u在一个奇圈上的条件满足;若u在一个偶圈上,则u到v必然存在一奇一偶两条路径,则可以形成一个新的奇圈。因此,双连通块中的结点u必然在一个奇圈上。

代码:

#include <vector>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define maxn 1005
#define maxm 1000005

using namespace std;

struct Edge{
    int u,v;
    Edge(int u=0,int v=0):u(u),v(v){}
}e[maxm];
int n,m,stamp,dfn[maxn],low[maxn],iscut[maxn],bccno[maxn];
int scnt,stack[maxm],bcc_cnt;
vector<int> vec[maxn], bcc[maxn];

void tarjan(int index,int fa)
{
    int child=0,tmp;
    dfn[index]=low[index]=++stamp;
    for(int i=0;i<vec[index].size();i++)
    {
        tmp=e[vec[index][i]].v;
        if(!dfn[tmp])
        {
            stack[++scnt]=vec[index][i], child++;
            tarjan(tmp,index);
            low[index]=min(low[index],low[tmp]);
            if(low[tmp]>=low[index])
            {
                iscut[index]=1;
                bcc[++bcc_cnt].clear();
                while(1)
                {
                    int num=stack[scnt--];
                    if(bccno[e[num].u]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(e[num].u);
                        bccno[e[num].u]=bcc_cnt;
                    }
                    if(bccno[e[num].v]!=bcc_cnt)
                    {
                        bcc[bcc_cnt].push_back(e[num].v);
                        bccno[e[num].v]=bcc_cnt;
                    }
                    if(e[num].u==index&&e[num].v==tmp)
                        break;
                }
            }
        }
        else if(dfn[tmp]<dfn[index]&&tmp!=fa)
        {
            stack[++scnt]=vec[index][i];
            low[index]=min(low[index],dfn[tmp]);
        }
    }
    if(fa<0&&child==1)
        iscut[index]=0;
}

void find_bcc()
{
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(iscut,0,sizeof(iscut));
    memset(bccno,0,sizeof(bccno));
    memset(bcc,0,sizeof(bcc));
    stamp=scnt=bcc_cnt=0;
    for(int i=1;i<=n;i++)
        if(!dfn[i])
            tarjan(i,-1);
}

int odd[maxn],color[maxn],b,a[maxn][maxn];
bool dfs(int index,int c)
{
    if(bccno[index]!=b)
        return 1;
    color[index]=c;
    for(int i=0;i<vec[index].size();i++)
    {
        int tmp=e[vec[index][i]].v;
        if(color[index]==color[tmp])
            return 0;
        if(!color[tmp]&&!dfs(tmp,3-c))
            return 0;
    }
    return 1;
}

int main()
{
    while(~scanf("%d %d",&n,&m)&&n+m)
    {
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
            vec[i].clear();
        int x,y;
        while(m--)
        {
            scanf("%d %d",&x,&y);
            a[x][y]=a[y][x]=1;
        }
        int len=0;
        for(int i=1;i<=n;i++)
        {
            for(int j=i+1;j<=n;j++)
            {
                if(!a[i][j])
                {
                    e[len]=Edge(i,j);
                    vec[i].push_back(len++);
                    e[len]=Edge(j,i);
                    vec[j].push_back(len++);
                }
            }
        }

        find_bcc();

        memset(odd,0,sizeof(odd));
        for(int i=1;i<=bcc_cnt;i++)
        {
            memset(color,0,sizeof(color));
            b=i;
            for(int j=0;j<bcc[i].size();j++)
                bccno[bcc[i][j]]=i;
            if(!dfs(bcc[i][0], 1))
                for(int j=0;j<bcc[i].size();j++)
                    odd[bcc[i][j]]=1;
        }

        int ans=0;
        for(int i=1;i<=n;i++)
            if(!odd[i])
                ans++;
        printf("%d\n",ans);
    }

    return 0;
}

总结:
图论的题目除了模板题,总是需要自己去推导一些简单的定理或者规律,难一点的题目暴力破解总是不行的。

你可能感兴趣的:(UVALIVE 3523(双连通分量+二分图染色))