这是一道很美妙的题…
1100E. Andrew and Taxi
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
1 ≤ ui, vi ≤ V≤ 105, 1 ≤ E ≤ 105, 1≤wi≤109
输出
输出两行。
第一行两个数,第一个是满足条件的最小值,第二个是需要反转的边的条数 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;
}