Codeforces Round #532 (Div. 2) E. Andrew and Taxi 拓扑序

题目链接:http://codeforces.com/contest/1100/problem/E

 

题意:

       给你一个n个点m条边的有向图,每条边有一个额定人数x,现在要你找到a个人,对于每条边,如果人数a大于这条边的额定人数xi,那么就可以改变这条路的方向,现在要你找出一个最小的权值a,并输出改变几条边和哪些边,使得这个有向图中没有环。

 

 

       二分肯定是妥妥的需要的,但是知道了这个人数之后该怎么验证是否会出现环呢,一开始是想着搜索,但是搜索出的结果只能是最小的权值是谁,对于每条边是否能修改好像还是不能做。这个地方实在是卡了我好久,拓扑也是参考了大佬的做法的。。感觉好奇妙好神奇。拓扑序的概念还是比较少碰到的,所以这次就留下一篇题解。

       做法: 

       在验证这个答案的时候,对于每一条权值小于你当前mid的时候,你不能加边,这条边就等于是可变可不变的,对于剩下的不能改变的边,先做一次拓扑,注意,如果拓扑完成后还有点的入度不为0的,那么就是这个图还存在环,就是mid太小。以此类推我们可以找到这个最小的最大需要人数,找到这个人数之后,我们就可以对这个数进行判断,如何判断,我们肯定这个答案下的图是没有环了的,那么我们就可以进行拓扑排序,得到每个点的拓扑序,对于每一条待修改的边,我们需要比较这条边的起点和终点的拓扑序,如果起点的大于终点,说明这条边在拓扑时起点会比终点晚一点被遍历到,那么改变方向,就不会出现环。

 


#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=100005;
int n,m,mma,in[maxn],top[maxn];
struct node{
    int fr,to,val;
}e[maxn];
vector<int> ve[maxn],road;
int ck(int x){
    memset(in,0,sizeof(in));
    for(int i=1;i<=n;i++){ve[i].clear();}
    int now=0;
    for(int i=1;i<=m;i++){
        if(e[i].val>x){
            in[e[i].to]++;
            ve[e[i].fr].push_back(e[i].to);
        }
    }
    queue<int> q;

    for(int i=1;i<=n;i++){
        if(!in[i]){
            q.push(i);
            top[i]=++now;
        }
    }
    while(!q.empty()){
        int u=q.front(); q.pop();
        for(int i=0;i<ve[u].size();i++){

            int v=ve[u][i];
            //printf("from=%d to=%d \n",u,v);
            if(--in[v]==0){
                q.push(v);
                top[v]=++now;
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(in[i]) return 0;
    }
    return 1;
}

int main(){
    int x,y,z,mma=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&x,&y,&z);
        e[i].fr=x,e[i].to=y,e[i].val=z;
        mma=max(mma,z);
    }
    int l=0,r=mma,ans=-1;
    while(l<=r){
        int mid=(l+r)/2;
        if(ck(mid)) {ans=mid; r=mid-1;}
        else l=mid+1;
    }
    ck(ans);
    for(int i=1;i<=m;i++){
        if(e[i].val<=ans){
            if(top[e[i].fr]>top[e[i].to]){
                road.push_back(i);
            }
        }
    }
    printf("%d %d\n",ans,road.size());
    for(int i=0;i<road.size();i++)
        printf("%d%c",road[i],i==road.size()-1?'\n':' ');
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值