【二分】【拓扑排序】CF1100E Andrew and Taxi
【二分】【拓扑排序】CF1100E Andrew and Taxi
题目大意
给定一个有向图,改变其中某些边的方向,它将成为一个有向无环图。
现在求一个改变边方向的方案,使得所选边边权的最大值最小。
题目思路
题目只要求所选择的所有的边的最大边权的边的边权最小,而并没有再继续要求修改的边数最少,也就是说只要满足前者,同时构造出一个可行解就行。
-
二分:
若最大的边权等于x,则获得对所有小于等于x的边的修改的权利。进而,若答案为x+1,也拥有同样甚至更大的修改权利,所以也是可行的。
-
拓扑排序:
两个作用:
-
是否存在环
如果是环上的任意一点,则它始终无法加入到拓扑的队列中,于是加入过拓扑的队列的总点数小于总点数,则说明有环的存在。
若假设不进行修改的边存在环,则说明当前二分的答案是错误的。
-
拓扑序
要进行修改的边,可以认为是在一张图上先确定一定不修改的边后处在方向状态待定的边。
而当由拓扑序低的点指向拓扑序高的点,即可保证新构建的图不存在环。若与原来的指向产生冲突则需要进行修改,否则保留原本指向即可。
-
code
#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define rep(i,x,n) for(int i=x;i<n;i++)
#define repd(i,x,n) for(int i=x;i<=n;i++)
#define MAX 1000005
#define MOD 1000000007
using namespace std;
const int N = 3E5+5,M = 6E5+10;
int n,m;
struct edge{
int u,v,w,id,nxt;
}es[M];
int h[N],idx;
void add(int u,int v,int w,int id)
{
es[idx] = {u,v,w,id,h[u]};
h[u] = idx++;
}
int sta[N],du[N];
vector<int > modis;
bool check(int x)
{
for(int i=1;i<=n;i++) sta[i] = du[i] = 0;
for(int i=0;i<idx;i++)
if(es[i].w>x)//不动大的边, 将其保留下来
du[ es[i].v ]++;
queue<int > q;
for(int i=1;i<=n;i++)
if(!du[i]) q.push(i);
int cnt = 0;
while(q.size())
{
int t = q.front();q.pop();
sta[t] = ++cnt;
for(int i = h[t];~i;i=es[i].nxt)
{
int v = es[i].v;
if(es[i].w<=x) continue; //跳过暂时忽略掉的边
du[v]--;
if(!du[v]) q.push(v);
}
}
if(cnt!=n) return false;
//这里开始的modis都是满足条件的
modis.clear();
for(int i=0;i<idx;i++)
{
int u = es[i].u , v = es[i].v , w = es[i].w;
if(w>x) continue;
if(sta[u]>sta[v]) modis.push_back(es[i].id);
}
sort(modis.begin(),modis.end());
return true;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
memset(h,-1,sizeof(h));
cin>>n>>m;
int maxn = 0;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
maxn = max(maxn,w);
add(u,v,w,i);
}
int l = 0, r = maxn , ans;
while(l<=r)
{
int mid = l + r >> 1;
if(check(mid))
{
r = mid - 1;
ans = mid;
}
else l = mid + 1;
}
cout<<ans<<" "<<modis.size()<<endl;
for(int i=0;i<modis.size();i++)
cout<<modis[i]<<" ";
return 0;
}