Codeforces Mr. Kitayuta's Technology

题意:给定有向图,问是的图连同的最小数量的边是多少

思路:把有向图转换成无向图,dfs染色,处理出若干个无向图联通块。我们再考察每个无向图联通块中的有向边,如果他们构成了一个有向无环图(DAG),那么说明图中的点是可以拓扑排序的。那么使用点数减一条有向边就能构造出这个拓扑关系(按拓扑序连成一条链)。如果不是一个DAG,那么无法拓扑排序。那我可以将所有点连成一个环,这样任意两点之间都是可达的,需要的边数即使联通块包含的点数。判断DAG的方法就是类似拓扑排序,记录每个点的出度,先找一个出度是0的点,不断拆边,最后看看是不是所有的边都能拆掉。所以对于每个联通块,判断它的有向边是否构成了DAG,是的话对答案的贡献就是点数减一,不是的话贡献就是点数。

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#include<algorithm>
using namespace std;
const int maxn=100010;
int N,M;
vector<int> g[maxn],has[maxn],to[maxn];
int deg[maxn];
int idx;
int vis[maxn];
void dfs(int u)
{
    vis[u]=idx;
    int len=g[u].size();
    for(int i=0;i<len;i++)
    {
        int v=g[u][i];
        if(vis[v])continue;
        dfs(v);
    }
}
bool can(int id)
{
    int len=has[id].size();
    queue<int> q;
    for(int i=0;i<len;i++)
        if(deg[has[id][i]]==0)q.push(has[id][i]);
    while(!q.empty())
    {
        int u=q.front();q.pop();
        len=to[u].size();
        for(int i=0;i<len;i++)
        {
            deg[to[u][i]]--;
            if(!deg[to[u][i]])
                q.push(to[u][i]);
        }
    }
    len=has[id].size();
    for(int i=0;i<len;i++)
        if(deg[has[id][i]])return true;
    return false;
}
int main()
{
    while(scanf("%d%d",&N,&M)!=EOF)
    {
        int u,v;
        memset(deg,0,sizeof(deg));
        for(int i=0;i<=N;i++){to[i].clear();g[i].clear();has[i].clear();}
        for(int i=1;i<=M;i++)
        {
            scanf("%d%D",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
            to[v].push_back(u);
            deg[u]++;
        }
        idx=1;
        memset(vis,0,sizeof(vis));
        for(int i=1;i<=N;i++)
        {
            if(!vis[i])
            {
                dfs(i);
                idx++;
            }
        }
        for(int i=1;i<=N;i++)
            has[vis[i]].push_back(i);
        int ans=0;
        for(int i=1;i<idx;i++)
            if(can(i))ans++;
        printf("%d\n",N-idx+ 1+ans);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值