(CodeForce) E. Andrew and Taxi(二分+拓扑)

视频题解戳这里

传送门

题目大意:一个有向图,给你m条有向边,每条边都有权值(即修改这条边方向所要付出的代价),如果确定一个代价v,则权值小于等于vv的边都可以被修改。求使得图变得没有环所需要的最小v,改变边的个数,和一种方案。

解题思路:一开始有往二分这方面考虑,但是却不知如何二分,这个题二分的是最小代价,那判断条件是什么呢,小于等于代价v的边相当于无向边,我们可以随意控制其方向,但是大于v的那一部分我们无法掌控,有环那就是无法改变的,所以我们拓扑排序只去考虑那些大于v的边,如果最后还有点的入度不为0,那就是说明有环,二分的代价太低了,l=mid+1,反之r=mid-1。最后如何输出方案呢,我们需要一个top数组,这个数组代表在拓扑序列里的先后顺序,越后输出的越大。我们可以根据这个序列去判断那些边应该被翻转,举个例子,边edge,edge的起点s指向edge的终点e,而且满足edge的权值w小于v。但是在拓扑序列里,top[s]>top[e],(我们可以知道由s->e的边,在拓扑里肯定s在e之前的),那这条边的顺序就要被我们交换顺序。这个题也算基本解决了。

#include <bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f
const int maxn = 1e5+7;
int n,m,in[maxn],top[maxn];
vector<int> ee[maxn];
struct node{
	int s,e,w;
}ed[maxn];

bool pd(int mid){
	int cnt=0;
	queue<int> q;
	for(int i=1;i<=n;++i)
		ee[i].clear();
	memset(in,0,sizeof(in));
	
	for(int i=1;i<=m;++i){
		if(ed[i].w>mid){
			ee[ed[i].s].push_back(ed[i].e);
			in[ed[i].e]++;
		}
	}
	for(int i=1;i<=n;++i){
		if(!in[i]){
			q.push(i);
			top[i]=++cnt;
		}
	}
	while(!q.empty()){
		int u=q.front();
		q.pop();
		for(int i=0;i<(int)ee[u].size();++i){
			int t=ee[u][i];
			--in[t];
			if(!in[t])	q.push(t),top[t]=++cnt;
		}
	}
	for(int i=1;i<=n;++i){
		if(in[i])	return false;
	}
	return true;
}
int main(){
	std::ios::sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=m;++i){
		cin>>ed[i].s>>ed[i].e>>ed[i].w;
	}
	int l=0,r=inf,res;
	while(l<=r){
		int mid=(l+r)>>1;
		if(pd(mid))	r=mid-1,res=mid;
		else l=mid+1;
	}
	pd(res); //抱住top序列是由res跑出 
	vector<int> ans;
	for(int i=1;i<=m;++i){
		if(ed[i].w<=res && top[ed[i].s] > top[ed[i].e]){
			ans.push_back(i);
		}
	}
	cout<<res<<" "<<(int)ans.size()<<endl;
	for(int i=0;i<(int)ans.size();++i){
		cout<<ans[i]<<" ";
	}
	
	return 0;
}

 

学习于https://blog.csdn.net/j2_o2/article/details/86469472

猜你喜欢

转载自blog.csdn.net/TDD_Master/article/details/86480160