zoukankan      html  css  js  c++  java
  • [洛谷P5304][GXOI/GZOI2019]旅行者

    题目大意: 有一张 (n(nleqslant10^5)) 个点 (m(mleqslant5 imes10^5)) 条边的有向有正权图,有(k(2leqslant kleqslant n))个关键点。问图中最近的两个关键点的距离。多组数据。

    题解: 有两种方法。

    1. 可对(k)个关键点二进制分组,可以知道(x)(y)不同至少满足二进制上有一位不同,每次按二进制中的一位分成两组,,建一个虚点连向所有的源点,跑 (dijkstra) ,总共跑 (log_2n) 次。复杂度 (O(nlog_2^2n)) 。代码我没写

    2. (F_i) 表示可以走到第(i)个点的最近关键点,(f_i)(dis_{F_i,i})(T_i) 表示第(i)个点可以走到的最近关键点,(t_i)(dis_{i.T_i})由题解得,枚举一条边 ((u,v,w)) ,若 (F_u ot=T_v) ,那么用 (f_u+w+t_v) 更新答案。这些数组可以在正反图上跑以所有关键点为源点的最短路径得到。接下来证明这样为什么是对的。

      很显然可以证明,若最短路径为 (x o y) ,其中边 ((u,v,w)) 在这条路径上且 (F_u ot=T_v) ,那么 (F_u=x,T_v=y) (假定最短路径只有一条),则 (f_u+w+t_v=ans) 。也就是只要证明(x o y)的路径上至少有一条边满足(F_u ot=T_v)

      采用反证的方法。比如一张图如下,其中(1 o n)为最短路径,方点为关键点,设边权均为(w)

      graph LR; A[1] B((2)) C((3)) D((n-1)) E[n] A--A-->B B--B-->C C-.-D D--Z-->E

      对于边(A)来说,(F_u=1,T_v=n),要破坏,因为(F_u)无法改变,只能改变(T_v),可以连一条(2 o 1)的边,边权小于(w),那么边(A)就无法对答案产生贡献。再考虑边(B)(F_u=1,T_v=n),要破坏,有两种方法,一种是连一条(n o 2)的边权小于(w)的边,另一种是连一条(3 o 1)的边权小于(w)的边。由于第一种方案会产生一条(n o 2 o 1)的更短的路径,所以排去,只能用第二种。以此类推,最后会连一条(n o 1)的边导致出现了更短的路径。与原来的假设矛盾。故原命题成立

    卡点:

    C++ Code:

    #include <iostream>
    #include <algorithm>
    #include <cstring>
    #define maxn 100010
    #define maxm 500010
    
    int cnt, head[maxn], _head[maxn];
    struct Edge {
    	int to, nxt, w;
    } e[maxm], _e[maxm];
    inline void addedge(int a, int b, int c) {
    	e[++cnt] = (Edge) {b, head[a], c}; head[a] = cnt;
    	_e[cnt] = (Edge) {a, _head[b], c}; _head[b] = cnt;
    }
    
    int Tim, n, m, k;
    int To[maxn], _To[maxn];
    long long dis[maxn], _dis[maxn];
    bool mar[maxn];
    
    namespace Graph {
    	int V[maxn << 2];
    	long long *Dis;
    
    	inline int getmin(int a, int b) { return Dis[a] < Dis[b] ? a : b; }
    	void modify(int rt, int l, int r, int p, int num) {
    		if (l == r) {
    			V[rt] = num;
    			return ;
    		}
    		const int mid = l + r >> 1;
    		if (p <= mid) modify(rt << 1, l, mid, p, num);
    		else modify(rt << 1 | 1, mid + 1, r, p, num);
    		V[rt] = getmin(V[rt << 1], V[rt << 1 | 1]);
    	}
    
    	void dijkstra(int *head, Edge *e, int *To, long long *dis) {
    		const int SZ = maxn << 3;
    		memset(dis, 0x3f, SZ);
    		Dis = dis;
    		memset(V, 0, sizeof V);
    		for (int i = 1; i <= n; ++i) To[i] = i;
    		for (int i = 1; i <= n; ++i) if (mar[i]) {
    			dis[i] = 0;
    			modify(1, 1, n, i, i);
    		}
    		for (int Tim = n; Tim; --Tim) {
    			int u = V[1];
    			modify(1, 1, n, u, 0);
    			for (int i = head[u]; i; i = e[i].nxt) {
    				int v = e[i].to;
    				if (dis[v] > dis[u] + e[i].w) {
    					dis[v] = dis[u] + e[i].w;
    					To[v] = To[u];
    					modify(1, 1, n, v, v);
    				}
    			}
    		}
    	}
    }
    using Graph::dijkstra;
    
    int main() {
    	std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
    	std::cin >> Tim;
    	while (Tim --> 0) {
    		std::cin >> n >> m >> k;
    		for (int i = 0, a, b, c; i < m; ++i) {
    			std::cin >> a >> b >> c;
    			addedge(a, b, c);
    		}
    		memset(mar, false, sizeof mar);
    		for (int i = 0, x; i < k; ++i) std::cin >> x, mar[x] = true;
    		dijkstra(_head, _e, To, dis);
    		dijkstra(head, e, _To, _dis);
    		long long ans = 0x3f3f3f3f3f3f3f3f;
    		for (int i = 1, u, v; i <= m; ++i) {
    			u = _e[i].to, v = e[i].to;
    			if (_To[u] != To[v]) ans = std::min(ans, _dis[u] + e[i].w + dis[v]);
    		}
    		std::cout << ans << '
    ';
    		if (Tim) {
    			memset(head, 0, sizeof head);
    			memset(_head, 0, sizeof _head);
    			cnt = 0;
    		}
    	}
    	return 0;
    }
    
    
  • 相关阅读:
    C# 中的高性能计时器(Daniel Strigl著,野比译)(转)
    C#中SerialPort类 随笔
    迭代器
    枚举数与可枚举类型(笔记)
    jQuery的关注与学习
    敏捷开发的4句宣言 大数据
    bat脚本
    c++动态库中回调函数使用
    C++中遍历lua table
    vs2010编译release版本却依赖debug版本库msvcrd100.dll
  • 原文地址:https://www.cnblogs.com/Memory-of-winter/p/11071827.html
Copyright © 2011-2022 走看看