#牛客网 通知小弟 (tarjan + 思维)

在战争时期,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。

示例1

输入

复制

3 2
1 2
1 2
1 1
0

输出

-1

示例2

3 1
1
2 2 3
0
0

输出

1

题目大意 : 输入一个有向图, 并给出m, 表示 你可以和m个人联系, 问你最少需要联系多少个人, 才能联系上所有人?

思路 :  不难看出这是一个强连通题,这道题的关键在于你能够联系的人是不是包含了所有需要被联系的人, 如果不是的话, 那么就无法联系所有人, 直接输出 -1就好。 那么需要被联系的人具有什么特点呢?写了一大堆水题的我碰到tarjan第一反应就是缩点后入度和出度的问题, 碰巧这题也和这有关 :

以这张图为例, 如果你能够通知的人只有1或者2, 那么就无法通知到所有人了,如果你1 、 2 都能通知, 那么最少通知的人数就是2。不难看出, 缩点后入度为0的点都是需要被通知的点, 他们的总和就是你至少要通知的人数了, 因为入度不为0的点都由其他人来通知,这点还是比较清楚的。

最后就是你可以通知的人的存储方式, 这里我选择的是map, 把你可以通知的人的second都 + 1, 为0 的都是不可通知的,在最后遍历入度为0的点的时候, 如果该点的second的值不为空的话, sum ++, 否则直接输出-1 就好。

如果看不太明白的话, 可以看看我其他博文, 最近一个月都在写图论, tarjan的题也写了不少 ,有水题也有一般难度的, 希望可以帮到你!

AC代码 : 

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 5;

struct node
{
    int v, next;
}e[maxn];
int dfn[maxn], low[maxn], suo[maxn], t, x;
int head[maxn], in[maxn], n, m, cnt, tot, scnt;
bool vis[maxn];
stack <int> st;
map <int, int> mp;
void init() {
    memset(dfn, 0, sizeof(dfn));
    memset(low, 0, sizeof(low));
    memset(head, -1, sizeof(head));
    memset(suo, 0, sizeof(suo));
    memset(in, 0, sizeof(in));
    memset(vis, 0, sizeof(vis));
    cnt = tot = scnt = 0;
}
void add (int from, int to) {
    e[++cnt].v = to;
    e[cnt].next = head[from];
    head[from] = cnt;
}
void tarjan(int x) {
    dfn[x] = low[x] = ++tot;
    vis[x] = 1;
    st.push(x);
    for (int i = head[x]; i != -1; i = e[i].next) {
        if (!dfn[e[i].v]) {
            tarjan (e[i].v);
            low[x] = min (low[x], low[e[i].v]);
        }
        else if (vis[e[i].v]) low[x] = min (low[x], dfn[e[i].v]);
    }
    if (dfn[x] == low[x]) {
        scnt++;
        int k;
        do {
            k = st.top();
            st.pop();
            suo[k] = scnt;
            vis[k] = 0;
        }
        while (k != x);
    }
}

int main()
{
    while (cin >> n >> m) {
        init();
        for (int i = 0; i < m; i++) {
            cin >> t;
            mp[t]++;
        }
        for (int i = 1; i <= n; i++) {
            cin >> t;
            while (t--) {
                cin >> x;
                add (i, x);
            }
        }
        for (int i = 1; i <= n; i++)  {
            if (!dfn[i]) tarjan(i);
        }
        for (int i = 1; i <= n; i++) {
            for (int j = head[i]; j != -1; j = e[j].next) {
                int u = suo[i];
                int v = suo[e[j].v];
                if (u != v) in[v]++; //对缩点后的点进行入度更新
            }
        }
        int flag = 1, sum = 0;
        for (int i = 1; i <= scnt; i++) {
            if (!in[i]) {  //入度为0, 需要被通知
                int ans = 0; 
                for (auto it : mp) { // C++11以上可以使用的遍历器
                    if (suo[it.first] == i) {  second值由i, 表示可以通知到
                        ans = 1;
                        break;
                    }
                }
                if (!ans) {flag = 0; break;}
                else sum++;
            }
        }
        if (flag) cout << sum << endl;
        else cout << -1 << endl;
        mp.clear();   //注意map的清空, 否则会出错
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43851525/article/details/91871638