【CodeForces 1100E】二分答案 | 拓扑排序 | E

这是一道很美妙的题…

1100E. Andrew and Taxi

time limit per test: 2 seconds
memory limit per test: 256 megabytes
input: standard input
output: standard output

Tags:二分答案 拓扑排序


题目描述

给定一个有向图,每条边有一个阈值。

你手上拿了一个值。对于每条边,如果其阈值 ≤ \le 你手上的值,你就可以把这条边反向(也可以选择放弃)

最小化你手上拿的值(需保证你能够让整个图无环)。



输入

1 1 1 组。第一行两个数, V V V E E E,分别代表顶点个数和边条数。

接下来 E E E 行每行 3 3 3 个数,代表一条有向边的起点 u i u_i ui、终点 v i v_i vi、阈值 w i w_i wi

范围: 1   ≤   u i ,   v i   ≤   V ≤   1 0 5 ,     1   ≤   E   ≤   1 0 5 ,     1 ≤ w i ≤ 1 0 9 1 ≤ u_i,\ v_i ≤ V≤ 10^5,\ \ \ 1 ≤ E ≤ 10^5,\ \ \ 1 ≤ w_i ≤ 10^9 1ui, viV105,   1E105,   1wi109


输出

输出两行。

第一行两个数,第一个是满足条件的最小值,第二个是需要反转的边的条数 n r e v n_{rev} nrev

第二行 n r e v n_{rev} nrev 个数,是需要反转的边的编号(按输入顺序编号,从 1 1 1 E E E



输入样例 1

5 6
2 1 1
5 2 6
2 3 2
3 4 3
4 5 5
1 5 4

输出样例 1

2 2
1 3 

输入样例 2

5 7
2 1 5
3 2 3
1 3 3
2 4 1
4 3 5
5 4 1
1 5 3

输出样例 2

3 3
3 4 7 



分析


思路分析

可以想到的是,我们去验证答案是容易的。又是要最小化答案,所以就 二分答案 啦。

左端点显然是 0 0 0,右端点是 max ⁡ { w i } \max\{w_i\} max{wi}。当然开心的话也可以离散化,缩小一下右端点。

那具体怎么 check 呢?拿到一个答案 a n s ans ans 后,我们仅考虑阈值 > a n s > ans >ans 的那些边,然后去 check 图中有没有环即可。

检查有没有环可以 拓扑排序 ,当然也可以直接 DFS (单纯检测环会更快一点)。

得到最小答案后,我们再看具体反转哪些边。这个时候就必须要每个点的拓扑序号了,所以必须得 拓扑排序



小细节

① 为什么这样的 check 函数(把反转操作“视为”删除操作)是正确的:

  • 1.如果这条边是稠环的公共边,并且两个环的方向还相反(顺逆时针),那么此时反转和删除操作不等价(反转的话仍然会保留其中一个稠环,而删除后原来两个稠环就都被破坏了)。但万幸的是这种情况下我们的 check 函数还是正确的(函数会返回 false 因为删除边后还有个大环呢,而正确结果正是 false 因为不管怎么反向都会有一个稠环得到保留)
  • 2.如果非以上情况,那么显然反转等价于删除(对环的破坏等价于删除)。check 函数能正确 work。

② 在二分得到最小答案后,要怎么得到具体反转方案:

显然不能把阈值 ≤ a n s \le ans ans 的边统统反转… 因为那样可能会产生新环的。我们可以利用 拓扑排序 获取每个点的拓扑序号,然后遍历边集,如果这条边的阈值是 ≤ a n s \le ans ans 并且起点的拓扑序号 > > > 终点的,就需要反转。


③ 为什么以上得到具体方案的做法是正确的:

  • 1.首先这个最小答案必满足 check(不用担心无解,至少取阈值最大值是 ok 的)。所以阈值 > a n s > ans >ans 的边构成的图是无环的。
  • 2.我们再添上阈值 ≤ a n s \le ans ans 的边。我们添边过程保证起点的拓扑序号是小于终点的(如果大于就反转),所以肯定添加后不会产生新环。

所以整体就一定是无环的啦。



时间复杂度
  • 二分答案,外层 O (   log ⁡ ( max ⁡ { w i } )   ) O(\ \log(\max\{w_i\})\ ) O( log(max{wi}) ) 的。
  • check 检测环, O ( V + E ) O(V+E) O(V+E) 的。
  • 总时间复杂度: O (   ( V + E ) log ⁡ ( max ⁡ { w i } )   ) O(\ (V+E)\log(\max\{w_i\})\ ) O( (V+E)log(max{wi}) )




AC代码

为了练手,这里的 check 函数就不也用拓扑排序了(反正后面求答案要拓扑排序的),而用 DFS 染色法求环(而且还能更快一点呢)

#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<ctime>
#include<string>
#include<algorithm>
#include<utility>
#include<vector>
#include<list>
#include<map>
#include<set>
#include<unordered_map>
#include<unordered_set>
#include<bitset>
#include<cctype>
#include<climits>

#define GC getchar()
#define _SN(x) {char _c=GC,_v=1;for(x=0;_c<48||_c>57;_c=GC)if(_c==45)_v=-1;for(;_c>=48&&_c<=57;x=(x<<1)+(x<<3)+_c-48,_c=GC);if(_v==-1)x=-x;}
#define _SAN(a,n) {auto _i=0,_n=n;for(;_i<_n;++_i)_SN(a[_i])}
#define _SA(a,l,r) {auto _i=l,_r=r;for(;_i<_r;++_i)_SN(a[_i])}
#define _gS(_1, _2, _3, _sc, ...) _sc
#define sc(...) _gS(__VA_ARGS__,_SA,_SAN,_SN, ...)(__VA_ARGS__)
#define _G1(_1) int _1;sc(_1)
#define _G2(_1,_2) int _1,_2;sc(_1)sc(_2)
#define _G3(_1,_2,_3) int _1,_2,_3;sc(_1)sc(_2)sc(_3)
#define _gG(_1,_2,_3,_get, ...) _get
#define get(...) _gG(__VA_ARGS__,_G3,_G2,_G1, ...)(__VA_ARGS__)
#define _F0N(i,n) for(auto i=0;i<n;++i)
#define _FLR(i,l,r) for(auto i=l,_r=r;i<_r;++i)
#define _gF(_1, _2, _3, _F, ...) _F
#define F(...) _gF(__VA_ARGS__,_FLR,_F0N, ...)(__VA_ARGS__)
#define _FD0(i,n) for(auto i=0;i<=n;++i)
#define _FDL(i,l,r) for(auto i=l,_r=r;i<=_r;++i)
#define _gFD(_1, _2, _3, _FD, ...) _FD
#define FD(...) _gFD(__VA_ARGS__,_FDL,_FD0, ...)(__VA_ARGS__)

#define OPER1(T,x1,b1) inline bool operator<(const T&o)const{return x1 b1 o.x1;}
#define OPER2(T,x1,b1,x2,b2) inline bool operator<(const T&o)const{return x1 b1 o.x1||x1==o.x1&&x2 b2 o.x2;}
#define OPER3(T,x1,b1,x2,b2,x3,b3) inline bool operator<(const T&o)const{return x1 b1 o.x1||x1==o.x1&&(x2 b2 o.x2||x2==o.x2&&x3 b3 o.x3);}

#define LL long long
#define ULL unsigned long long
#define PC putchar
template<class T>
void PRT(const T _){if(_<0){PC(45),PRT(-_);return;}if(_>=10)PRT(_/10);PC(_%10+48);}
template<class T>
void UPRT(const T _){if(_>=10)UPRT(_/10);PC(_%10+48);}

#define CON constexpr
#define T_CASE int CASE;sc(CASE)for(int __=1;__<=CASE;++__)
#define cincout std::cin.tie(nullptr),std::cout.tie(nullptr),std::ios::sync_with_stdio(false);
#define eps 1e-8
#define PI 3.141592653589793
#define MAX_INT 2147483647
#define MIN_INT -2147483648
#define MAX_LL 9223372036854775807
#define MIN_LL -9223372036854775808
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3fLL
#define endl '\n'
#define priority_queue priority_queue
#define PQ std::priority_queue
#define PR std::pair
#define vector vector
#define VI std::vector<int>
#define MII std::map<int,int>
#define MLI std::map<LL,int>
#define MSI std::map<std::string,int>
#define PII std::pair<int,int>
#define PLI std::pair<LL,int>
#define PSI std::pair<std::string,int>
#define MPFD(k) auto it=mp.find(k)

#define MIN(a, b) ((a)<(b)?(a):(b))
#define MIN3(a, b, c) (MIN(a, MIN(b, c)))
#define MAX(a, b) ((a)>(b)?(a):(b))
#define MAX3(a, b, c) (MAX(a, MAX(b, c)))
#define get_max(a,l,r,_max) auto _max=a[l];for(int _i=l+1,_r=r;_i<_r;++_i)if(_max<a[_i])_max=a[_i]
#define get_min(a,l,r,_min) auto _min=a[l];for(int _i=l+1,_r=r;_i<_r;++_i)if(_min<a[_i])_min=a[_i]
#define ABS(a) ((a)>0?(a):-(a))
#define FABS(a) ((a)>0?(a):-(a))
#define log2n(x) (log(x)/0.69314718055995)
#define MHD(p1, p2) ((p1.x>p2.x?p1.x-p2.x:p2.x-p1.x)+(p1.y>p2.y?p1.y-p2.y:p2.y-p1.y))

#define PB emplace_back
#define EB emplace_back
#define BRK else break
#define ALL(X) (X).begin(),(X).end()
#define SORT(X) std::sort(ALL(X))
#define SORTD(X) std::sort(ALL(X),std::greater<decltype((X)[0])>())
#define swap(a, b) do{auto _t=a; a=b; b=_t;}while(0)
#define mem0(a) memset(a,0,sizeof(a))
#define memf1(a) memset(a,-1,sizeof(a))
#define meminf(a) memset(a,0x3f,sizeof(a))

CON int MV(1e5+7), ME(1e5+7);

typedef int xint;
typedef int dint;

struct Edge
{
	xint next, u, v;
	dint d;
} ed[ME];
xint head[MV], tot;
#define edd(u, v, d) ed[++tot].next=head[u], ed[tot].u=u, ed[tot].v=v, ed[tot].d=d, head[u]=tot


xint V, E;
dint k;

constexpr char IN_STA(1), VIS(2);
char vis[MV];

bool dfs(const xint u)
{
	if (vis[u])
		return vis[u] == IN_STA;

	vis[u] = IN_STA;
	for (int i=head[u]; i; i=ed[i].next)
		if (ed[i].d > k && dfs(ed[i].v))
			return true;
	vis[u] = VIS;

	return false;
}

bool check(void)
{
	memset(vis, false, sizeof(*vis) * (V+1));

	FD(i, 1, V)
		if (!vis[i] && dfs(i))
			return false;

	return true;
}


xint ind[MV], q[MV];
xint topo[MV], tpn;

void topo_sort(void)
{
	FD(e, 1, tot)
		if (ed[e].d > k)
			++ind[ed[e].v];

	xint hd = 0, tl = 0;
	FD(v, 1, V)
		if (!ind[v])
			q[tl++] = v, topo[v] = ++tpn;

	xint u, v;
	while (hd < tl)
	{
		u = q[hd++];
		for (int i=head[u]; i; i=ed[i].next)
		{
			if (ed[i].d > k)
			{
				v = ed[i].v;
				if (--ind[v] == 0)
					q[tl++] = v, topo[v] = ++tpn;
			}
		}
	}
}


void print_ans(void)
{
	VI v;
	FD(e, 1, tot)
		if (topo[ed[e].u] > topo[ed[e].v])
			v.EB(e);
	UPRT(v.size()), PC(10);

	for (int & e : v)
		UPRT(e), PC(32);
}


int main()
{
	sc(V)
	sc(E)
	int maxd = 0;

	F(i, E)
	{
		get(u, v, d)
		edd(u, v, d);
		if (maxd < d)
			maxd = d;
	}

	dint L = 0, R = maxd;
	while (L <= R)
	{
		k = L+R>>1;
		if (check())
			R = k-1;
		else
			L = k+1;
	}

	k = L;			// 必须对k赋L的值,不能就让k保持退出循环时的值。因为退出循环时k不一定等于L
					// 比如最后一次循环时L是5,R是5,然后k是5,然后假设check失败了,然后L被赋6,然后退出循环
					// 此时正确的最终答案应该是L,即为6(废话,不然这个二分答案的板子就错了qwq)
					// 而此时k是5,并不是L的值。所以【一定要】记得k=L
	topo_sort();
	UPRT(L), PC(32);

	print_ans();

	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值