【训练题】强连通分量缩点 P1679

Description

有 N 个人和每个人所认识人的列表,注意:即使B在A的列表中,A也不一定在B的列表中。现在小明有一个重要消息要通知这N个人,注意:如果A认识B,则当A得到这个消息,他就会立即通知B。


现在请你完成下面两个任务:


任务1:请你计算要让N个人都得到消息,那么小明必须把这个消息直接通知的人的最少数目。


任务2:如果小明想要只告诉这N个人中的任何一个人,其他所有人都能得到消息,那么可能需要在某些人的认识列表添加认识的新成员。请你计算,最少添加多少新成员,就可以让任何一个人得到消息,都能传到其他所有人。


Input

第一行包括一个整数 N:表示人数,这些人编号依次为1..N。接下来 N 行中每行都表示一个认识关系的列表,第 i+1 行包括表示第 i 人认识的人的列表,每个列表用 0 结束,空列表只用一个 0 表示。


Output

第一行包括一个正整数:任务 1 的解。第二行应该包括任务2 的解。


Hint

N<=10000


Solution

首先这道题要用tarjan缩点成一个DAG图,之后统计入度和出度为1的点,这些点就需要一条边来形成环。边的数量取入度和出度为1的点的数量的最大值就可以了因为加1条边可以分别处理一个入度为1和出度为1的点,单独的点就需要单独一条边来处理。tarjan找强连通分量的原理是把点压进栈里进行dfs,对dfn值为0的点进行tarjan更新dfn这个时间戳,把low设置为u和v里面low值最小的,然后当v还没有属于任何一个强连通分量的时候,把s的low值更新成为low[u]和dfn[v]里的最小值。如果他们属于同一个连通分量他们的low值都是祖先结点的low值。这个过程类似于动态规划。当找到一条返祖边,也就是low[s]==dfn[s]的时候,就把在s之前的点全部弹出并记录在一个强连通分量里直到把s弹出来。


由于图不一定连通而一个孤点也是一个强连通分量所以只要!dfn[i]就要进行tarjan。最后枚举所有边,要是有不在同一个强连通分量的两个点之间有边,起点这个强连通分量的出度就++,终点的强连通分量的入度就++。然后枚举缩点后的所有强连通分量,用ans1和ans2记录入度为0和出度为0的点的数量,就完了。


注意事项:
1、如果所有的点都在一个强连通分量里面,就不需要加边,所以if(cnt==1)ans=0这个语句是必要的。

#include<cstdio>
#include<iostream>
#include<cstring>
#include<stack>
#include<algorithm>
#define maxn 1000010
using namespace std;
struct Edge{
    int u;
    int v;
    int next;
}edge[maxn];
stack<int>stk;
int first[maxn],last[maxn],low[maxn],dfn[maxn],belong[maxn],rd[maxn],cd[maxn];
int node,n,x,hhhh,cnt,ans1,ans2,ans;
void addedge(int u,int v){
    edge[++node]=(Edge){u,v,0};
    if(first[u]==0)first[u]=node;
    else edge[last[u]].next=node;
    last[u]=node;
    return;
}
void init(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        while(1){
            scanf("%d",&x);
            if(x==0)break;
            addedge(i,x);
        }
    }
    return;
}
void tarjan(int s){
    low[s]=dfn[s]=++hhhh;
    stk.push(s);
    for(int q=first[s];q;q=edge[q].next){
        int p=edge[q].v;
        if(dfn[p]==0){
            tarjan(p);
            low[s]=min(low[s],low[p]);
        }
        else if(!belong[p]){
            low[s]=min(low[s],dfn[p]);
        }
    }
    if(low[s]==dfn[s]){
        cnt++;
        belong[s]=cnt;
        while(stk.top()!=s){
            belong[stk.top()]=cnt;
            stk.pop();
        }
        stk.pop();
    }
}
void solve(){
    for(int i=1;i<=n;i++){
        if(!dfn[i]){
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++){
        for(int q=first[i];q;q=edge[q].next){
            int p=edge[q].v;
            if(belong[i]!=belong[p]){
                rd[belong[p]]++;
                cd[belong[i]]++;
            }
        }
    }
    for(int i=1;i<=cnt;i++){
        if(rd[i]==0)ans1++;
        if(cd[i]==0)ans2++;
    }
}
int main(){
    init();
    solve();
    printf("%d\n",ans1);
    ans=max(ans1,ans2);
    if(cnt==1)ans=0;
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/virtual-north-Illya/p/10045075.html
今日推荐