Codevs2822爱在心中题解

题目
  • 来源

https://vijos.org/p/1626
http://codevs.cn/problem/2822/

  • 题目描述 Description
    “每个人都拥有一个梦,即使彼此不相同,能够与你分享,无论失败成功都会感动。爱因为在心中,平凡而不平庸,世界就像迷宫,却又让我们此刻相逢Our Home。”
    在爱的国度里有N( N1000 )个人,在他们的心中都有着一个爱的名单,上面记载着他所爱的人(不会出现自爱的情况)。爱是具有传递性的,即如果A爱B,B爱C,则A也爱C。
    如果有这样一部分人,他们彼此都相爱,则他们就超越了一切的限制,用集体的爱化身成为一个爱心天使。
    现在,我们想知道在这个爱的国度里会出现多少爱心天使。而且,如果某个爱心天使被其他所有人或爱心天使所爱则请输出这个爱心天使是由哪些人构成的,否则输出-1。

  • 输入描述 Input Description
    第1行,两个数N、M,代表爱的国度里有N个人,爱的关系有M( M10000 )条。
    第2到第M+1行,每行两个数A、B,代表A爱B。

  • 输出描述 Output Description
    第1行,一个数,代表爱的国度里有多少爱心天使。
    第2行,如果某个爱心天使被其他所有人和爱心天使所爱则请输出这个爱心天使是由哪些人构成的(从小到大排序),否则输出-1。

  • 样例输入 Sample Input
    样例输入1:
    6 7
    1 2
    2 3
    3 2
    4 2
    4 5
    5 6
    6 4
    样例输入2:
    3 3
    1 2
    2 1
    2 3

  • 样例输出 Sample Output
    样例输出1:
    2
    2 3
    样例输出2:
    1
    -1

  • 数据范围及提示 Data Size & Hint
    各个测试点1s

题解
  • 第一问,不难想到是直接强连通分量缩点,再记录强连通分量的大小。爱心天使是包含结点个数大于等于2的强连通分量。权当练一发非递归tarjan了。

  • 第二问,初看起来有些复杂,但深入分析一下发现,其实也就那么回事。
    缩完点后,肯定不存在环。若存在一个被所有人爱的爱心天使,那在有向图中体现为所有的点都直接或间接指向这个强连通分量,这个强连通分量不能有出边并且有且只有一个,证明如下:
    1、若有某点不指向这个强连通分量,那由定义,这个强连通分量不是答案;
    2、若这个强连通分量有出边,设这条出边指向的终点为t,由1,t不可能不指向这个强连通分量本身,那这个强连通分量就包含点t,出现矛盾,故这个强连通分量不可能有出边;
    3、若这类强连通分量不唯一,那由2,这些强连通分量都没有出边,它们无法相连,这个爱心天使就不被所有人爱,不合题意,故若有这类强连通分量,有且只有一个。
    那我们tarjan缩完点后,记录每个强连通分量的出度。之后枚举所有强连通分量,记录出度为零的强连通分量的个数。若最终个数不为1或者其大小不大于1,则无解;否则输出这个强连通分量的所有点。

  • Code

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 1005
#define M 10005
#define nil 0
using namespace std;
int n, m, oud[N];
int u[M], v[M], nxt[M], pnt[N], e;
int s[N], top;
int S[N], TOP, lst[N];
int dfn[N], low[N], isin[N], sz[N], tot, indx;
bool ins[N];
inline void add(int a, int b)
{
    u[++e] = a; v[e] = b; nxt[e] = pnt[a]; pnt[a] = e;
}
void tarjan(int x)
{
    dfn[x] = low[x] = ++indx;
    S[++TOP] = s[++top] = x;
    ins[x] = true;
    while(TOP > 0)
    {
        int t = S[TOP];
        for(int i = lst[t]; i != nil; i = nxt[i])
        {
            if(dfn[v[i]] == 0)
            {
                lst[t] = nxt[i];
                dfn[v[i]] = low[v[i]] = ++indx;
                S[++TOP] = s[++top] = v[i];
                ins[v[i]] = true;
                break;
            }
        }
        if(t == S[TOP])
        {
            for(int i = pnt[t]; i != nil; i = nxt[i])
            {
                if(dfn[v[i]] > dfn[t]) low[t] = min(low[t], low[v[i]]);
                else if(ins[v[i]]) low[t] = min(low[t], dfn[v[i]]);
            }
            if(dfn[t] == low[t])
            {
                int j;
                ++tot;
                do
                {
                    j = s[top--];
                    ins[j] = false;
                    isin[j] = tot;
                    ++sz[tot];
                } while(j != t);
            }
            --TOP;
        }
    }
}
void init()
{
    e = top = TOP = tot = indx = 0;
    memset(u, 0, sizeof(u));
    memset(v, 0, sizeof(v));
    memset(nxt, 0, sizeof(nxt));
    memset(pnt, 0, sizeof(pnt));
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(isin, 0, sizeof(isin));
    memset(sz, 0, sizeof(sz));
    memset(ins, 0, sizeof(ins));
    memset(lst, 0, sizeof(lst));
    memset(oud, 0, sizeof(oud));
    int a, b;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; ++i)
    {
        scanf("%d%d", &a, &b);
        add(a, b);
    }
    for(int i = 1; i <= n; ++i) lst[i] = pnt[i];
}
void work()
{
    for(int i = 1; i <= n; ++i) if(dfn[i] == 0)
    {
        tarjan(i);
    }
    int ans = 0;
    for(int i = 1; i <= tot; ++i)
    {
        if(sz[i] > 1) ++ans;
    }
    printf("%d\n", ans);
    for(int i = 1; i <= n; ++i) for(int j = pnt[i]; j != nil; j = nxt[j])
    {
        if(isin[i] != isin[v[j]]) ++oud[isin[i]];
    }
    int cnt = 0, poi = 0;
    for(int i = 1; i <= tot; ++i) if(oud[i] == 0)
    {
        ++cnt; poi = i;
    }
    if(cnt != 1 || sz[poi] <= 1) puts("-1");
    else
    {
        for(int i = 1; i <= n; ++i)
        {
            if(isin[i] == poi) printf("%d ", i);
        }
    }
}
int main()
{
    init();
    work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值