[BZOJ 1064][NOI 2008]假面舞会(图论+BFS)

142 篇文章 0 订阅
41 篇文章 0 订阅

题目链接

http://www.lydsy.com/JudgeOnline/problem.php?id=1064

题目大意

给一个有向图染色,一个点的后继的颜色必须全部相同,问最多多少种颜色,最少多少种

思路

为了便于叙述,以下面具的种类数简称类数,图中不同的颜色代表不同类别的面具,一种面具简称一种颜色。
如果这个有向图是一棵树的话,那么树上每个点的儿子颜色必须都相同,如下图
这里写图片描述
手模下可以发现这种情况,合法的类数是离树根最深的点的深度-离树根最近的点的深度+1。具体的解释见下面一种情况。

如果这个有向图是个简单的环的话,那么合法的类数是环上点的个数的约数,如下图是一个点数为6的简单环,它有如下图两种不同的合法染色方案(类数为2的和类数为3的)。由于不同的颜色可以循环染色、循环利用,因此合法的类数是环上点的个数的约数,上面的一种情况与此类似。
这里写图片描述

但是如果这个有向图看上去是个简单环,但是它上面有些边反向了,这种情况下,我们把这个有向图转换成无向图来看待,它还是一个简单环,此时合法的类数是环上正向边个数-反向边个数的约数。如下图是一个例子,这个“简单环”有6条正向边,2条反向边,注意到一条反向边肯定和一条正向边头碰头地“抵消”掉了,把这些边和点缩成一个点,这时候这个“简单环”就会变成一个普通的有向图的简单环,变成了上面的情况。扩展到一般情况,连续的若干条反向边和连续的若干条正向边也会像头碰头一样“抵消”掉。
这里写图片描述
这里写图片描述

因此,我们可以将原来的有向图转为无向图(一条无向边=2条方向相反的有向边),与原图方向相同的有向边定义为正向边,边权为1,与原图反向的定义为反向边,边权为-1。
那么这样就比较方便了,一个环上的类数一定是这个环的长度的约数。

如果这个有向图转无向图后有环,就不需要考虑树的情况了;反之,如果没有环,那么整张图就是一个森林,对森林中每个树分开进行BFS,统计答案求和即可,这是类数最大值,类数最小值显然为3。

代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#include <queue>

#define MAXV 110000
#define MAXE 1100000

using namespace std;

int n,m;

struct edge
{
    int u,v,w,next;
}edges[2*MAXE];

int head[MAXV],nCount=0;

void AddEdge(int U,int V,int W)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].w=W;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

bool vis[MAXV];
int dist[MAXV];
int commongcd; //每个环上正向边数-反向边数的最大公约数,也就是面具种类数最大值

int gcd(int a,int b)
{
    if(!b) return a;
    return gcd(b,a%b);
}

int BFS(int S) //从S开始进行广搜
{
    int maxdist=0,mindist=0; //maxdist是这个联通块中S出发的最长链距离,mindist反之
    queue<int>q;
    while(!q.empty()) q.pop();
    dist[S]=0;
    vis[S]=true;
    q.push(S);
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(vis[v])
            {
                commongcd=gcd(commongcd,dist[u]+edges[p].w-dist[v]); //找到了一个环,那么这个环上的点的个数是最终答案(面具种类数)的倍数
                continue;
            }
            vis[v]=true;
            dist[v]=dist[u]+edges[p].w;
            maxdist=max(maxdist,dist[v]);
            mindist=min(mindist,dist[v]);
            q.push(v);
        }
    }
    return maxdist-mindist+1;
}



int main()
{
    int sum=0;
    memset(head,-1,sizeof(head));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        AddEdge(u,v,1);
        AddEdge(v,u,-1); //反向边边权为-1
    }
    for(int i=1;i<=n;i++)
        if(!vis[i])
            sum+=BFS(i);
    if(commongcd<0) commongcd=-commongcd; //注意最大公约数要取绝对值
    if(commongcd) //有环,就不需要考虑树
    {
        if(commongcd<3) //面具种类数最大不到3,那么输入数据有问题
        {
            printf("-1 -1\n");
            return 0;
        }
        int minans;
        for(minans=3;minans<commongcd&&commongcd%minans;minans++);
        printf("%d %d\n",commongcd,minans);
        return 0;
    }
    else if(sum<3) //否则就考虑树
    {
        printf("-1 -1\n");
        return 0;
    }
    printf("%d 3\n",sum);
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值