【2018年全国多校算法寒假训练营练习比赛(第四场)- E】通知小弟(强连通缩点)...

【2018年全国多校算法寒假训练营练习比赛(第四场)- E】通知小弟(强连通缩点)

题目链接:https://www.nowcoder.com/acm/contest/76/E

 

题目描述

        在战争时期,A国派出了许多间谍到其他国家去收集情报。因为间谍需要隐秘自己的身份,所以他们之间只是单向联系。所以,某个间谍只能单向联系到一部分的间谍。同时,间谍也不知道跟他联系的是谁。
HA是间谍们的老大,但他也只能联系到部分的间谍。HA现在有一项命令有告诉所有的间谍。HA想要知道他至少要告诉多少个他能联系上的间谍才能通知到所有的间谍。

输入描述:

有多个测试数据。
对于每个测试数据:
第一行为一个整数n,m(0<n,m<=500)代表间谍的数量和HA能通知到的间谍的数量(间谍的编号为1-n);
第二行为m个用空格隔开的整数xi,代表HA能通知到的间谍的编号;
第三行到第n+2行,每一行第一个整数ai(0<=ai<n)表示第i-2个间谍能单向联系到的间谍数。之后有ai个用空格隔开的整数,表示间谍i-2能单向联系到的间谍的编号。

输出描述:

  输出一行,此行中有一个整数,代表HA至少需要联系的间谍数。如果HA不能通知到所有间谍,输出-1。



【思路】
如果是dag,就看老大能不能通知到所有入度为0的点,能的话答案就是入度为0的点个数,不能就是-1。但是可能存在环,这样即使入度不为0也要有必须通知的人,说白了就是在一个强连通分量内。所以缩点后成为dag找入度为0的点即可。

【个人感悟】
懒癌发作,拖好久了。。本来是不会写这种题的,这两天又硬着头皮学了一波tarjan。。。用并查集写虽然也ac了,然鹅数据太水了没法处理环的情况,就不贴代码了 (*/ω\*)

【ac代码】
#include <bits/stdc++.h>
using namespace std;
const int N = 505;
int low[N], vis[N], dfn[N], col[N], b[N], in[N];
vector<int>V[N];
stack<int>s;
int n, cnt, num;
void dfs(int u)
{
    s.push(u);
    vis[u] = 1;
    dfn[u] = low[u] = ++cnt;
    for (int i = 0; i < V[u].size(); i++)
    {
        int v = V[u][i];
        if (!dfn[v])
        {
            dfs(v);
            low[u] = min(low[u], low[v]);
        }
        else if (vis[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u])
    {
        int t;
        num++;
        do
        {
            t = s.top();
            s.pop();
            col[t] = num;
            vis[t] = 0;
        }
        while (t != u);
    }
}

void tarjan()
{
    int i;
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(vis, 0, sizeof(vis));
    memset(col, 0, sizeof(col));
    while (!s.empty()) s.pop();
    cnt = num = 0;
    for (i = 1; i <= n; i++)
        if (!dfn[i]) dfs(i);
}
int main()
{
    int m, a, i, j, c;
    while(~scanf("%d%d", &n, &m))
    {
        for(i = 1; i <= n; i++) V[i].clear();
        for(i = 1; i <= m; i++) scanf("%d", &b[i]);
        sort(b+1, b+1+m);
        for(i = 1; i <= n; i++)
        {
            scanf("%d", &c);
            while(c--)
            {
                scanf("%d", &a);
                V[i].push_back(a);
            }
        }
        tarjan();
        for(i = 1; i <= n; i++)
            for(j = 0; j < V[i].size(); j++)
            {
                int v = V[i][j];
                if(col[i] != col[v]) in[col[v]]++; //缩点后入度++
            }
        int ans = 0, f = 0;
        for(i = 1; i <= num; i++)
        {
            if(!in[i])
            {
                f = 0;
                for(j = 1; j <= n; j++)
                {
                    //找入度为0的强连通分量内是否包含老大能通知的点
                    if(col[j] == i && binary_search(b+1, b+1+m, j))
                    {
                        ans++;
                        f = 1;
                        break;
                    }
                }
                if(f == 0)
                    break;
            }
        }
        printf("%d\n", f == 0?-1:ans);
    }
    return 0;
}

 

 

posted @ 2018-02-23 15:04 LesRoad 阅读(...) 评论(...) 编辑 收藏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值