CF1062F Upgrading Cities

洛谷题目

vjudge题目

这道题一眼看上去可能没什么思路至少我是这样的,但仔细思考后,就会发现可以用拓扑排序。

拓扑排序有一个性质,就是任意时刻在队列里的点都不能互相到达这不是显然吗,用这个性质,我们就可以求出每个点能到达的节点个数了。

但是重要的节点次重要的节点的定义是互相到达,于是我们要将原图的每条边反过来存一次图,暂且叫它“反图”,然后把反图做一遍拓扑排序,再把两次拓扑排序求出来的每个点可以到达的节点个数加起来,就得到了每个点可以和其它点互相到达的节点个数。

最后,把每个点能到达的节点个数判断一遍,如果一个点的个数$≥n-2$,那么这个点就是一个重要的节点或次重要的节点。

#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>

using namespace std;

const int maxn=300010;
vector<int>G1[maxn],G2[maxn];   //G1[]:原图的邻接表,G2[]:反图的邻接表
int n,m,ans;
int in1[maxn],in2[maxn];        //in1[]:原图每个点的入度,in2[]:反图每个点的入度
int d[maxn];                    //每个点能到达的节点个数

int read()      //快读 
{
    int x=0;
    char c=getchar();
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}

void tp1()      //拓扑排序原图
{
    queue<int>que;
    int sum=n;              //目前能到达的节点个数 
    for(int i=1;i<=n;i++)
        if(!in1[i])         //入度为零就入队 
        {
            que.push(i);
            sum--;          //因为i没有入度,所以能到达的节点个数减一 
        }
    while(!que.empty())
    {
        int t=que.front();
        que.pop();
        if(que.empty())
            d[t]+=sum;          //如果队列为空,那么t可以到达剩下所有的节点 
        if(que.size()==1)       //如果队列里剩一个节点,那么判断删去这个节点后t能否到达其余所有的节点  
        {
            int tt=que.front(); //tt:t不能到达的点 
            int flag=1;         //flag=1:t能到达其余所有的节点,flag=0:不能 
            for(int i=0;i<G1[tt].size();i++)//枚举tt能到达的点 
                if(in1[G1[tt][i]]==1)       //如果能到达G1[tt][i]的点只有一个,那么t就不能到达G1[tt][i] 
                {
                    flag=0;
                    break;
                }
            if(flag==1)
                d[t]+=sum;
        }
        //拓扑排序入队
        for(int i=0;i<G1[t].size();i++)
        {
            int c=G1[t][i];
            if(!(--in1[c]))
            {
                que.push(c);
                sum--;          //因为c入队了,所以后面的节点就都到不了c了
            }
        }
    }
    return;
}

void tp2()      //拓扑排序反图 
{
    queue<int>que;
    int sum=n;              //目前能到达的节点个数 
    for(int i=1;i<=n;i++)
        if(!in2[i])         //入度为零就入队 
        {
            que.push(i);
            sum--;          //因为i没有入度,所以能到达的节点个数减一 
        }
    while(!que.empty())
    {
        int t=que.front();
        que.pop();
        if(que.empty())
            d[t]+=sum;          //如果队列为空,那么t可以到达剩下所有的节点 
        if(que.size()==1)       //如果队列里剩一个节点,那么判断删去这个节点后t能否到达其余所有的节点
        {
            int tt=que.front(); //tt:t不能到达的点 
            int flag=1;         //flag=1:t能到达其余所有的节点,flag=0:不能 
            for(int i=0;i<G2[tt].size();i++)//枚举tt能到达的点 
                if(in2[G2[tt][i]]==1)       //如果能到达G1[tt][i]的点只有一个,那么t就不能到达G1[tt][i] 
                {
                    flag=0;
                    break;
                }
            if(flag==1)
                d[t]+=sum;
        }
        //拓扑排序入队
        for(int i=0;i<G2[t].size();i++)
        {
            int c=G2[t][i];
            if(!(--in2[c]))
            {
                que.push(c);
                sum--;          //因为c入队了,所以后面的节点就都到不了c了
            }
        }
    }
    return;
}

int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        G1[u].push_back(v);
        in1[v]++;
        G2[v].push_back(u);
        in2[u]++;
    }
    tp1();
    tp2();
    //如果第i个点能到达的节点个数≥n-2,那么i是重要的节点或此重要的节点 
    for(int i=1;i<=n;i++)
        if(d[i]>=n-2)
            ans++;
    printf("%d\n",ans);
    return 0;
}

 

posted @ 2020-04-22 08:19  Acestar  阅读(205)  评论(0编辑  收藏  举报