jzoj6426 旅行 (图论)

题意

给一个n个点m条边的有向图,每条边有边权的取值范围。给出一条点1到点2的路径,问在这条路径上先按顺序走多少条边后,再走到点2(长度要包括之前规划好的路径)一定没有直接从1号点开始优。
n , m ≤ 2 × 1 0 5 n,m\leq2\times10^5 n,m2×105
边权1e6

思路

  • GDKOI2018原题
  • LK真牛批
  • 考虑每条边的取值,可以感受到不是最大值就是最小值。若存在一种图G使得某种要求的走法是最短路,那么将这条路取最小值,其余取最大值,不会对最短路造成影响。
  • 注意到答案是可以二分的,现在就是要判断按照规定的路径走到x后,是否在某种图中存在x到2的最短路。
  • 类似上文的想法,搞一个贪心出来。规定路径上x之前的边都取最小值当然优。
  • 接下来的操作就很高端了:
  • 直觉地想象一下,最好是构造使得从1出发的路径取最大,从x出发的路径取最小。但是会有路径相交的问题。
  • 若路径要相交,则会有重点。有重点的情况是“无需讨论”的。因为走到重点的顺序就已经决定了他们接下来最终到2的顺序。因此某个点往后扩展时,必定只会由1或x中较小的(相同则是x)接手。
  • 自然地我们整出这样一个贪心想法:
  • 从1和x开始,初始dis[1]=0.dis[x]= ∑ L \sum L L,每次选出最小的一个点,若是被1占领的,则往外扩展取最大值。否则取最小值。但如果是规划好的前缀,则直接取最小值。最终终点若是x占领的,那么是存在这样一条最短路的。
  • 本质上就是一个dij操作。。。
  • 为了实现方便,将边权*2之后将dis[x]初始减一。这样方便优先x以及判断是谁占领。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n, m, p, tot;
int final[N], nex[N], to[N], L[N], R[N], no[N];
struct edge{
	int from, to, L, R;
} e[N];
int ed[N], pre[N];
ll dis[N];
bool ok(int mid) {
	static priority_queue<pair<ll,int> > pq;
	memset(dis, 127, sizeof dis);
	int ori = e[ed[mid]].to;
	dis[1] = dis[ori] = 0;
	for(int i = 1; i <= mid; i++) {
		dis[ori] += e[ed[i]].L;
	}
	dis[ori]--;
	pq.push(make_pair(-dis[1], 1));
	pq.push(make_pair(-dis[ori], ori));
	while(pq.size()){
		pair<ll,int> z = pq.top(); pq.pop();
		int x = z.second;
		if (dis[x] != -z.first) continue;
		for(int i = final[x]; i; i = nex[i]) {
			int y = to[i];
			ll ev = (dis[x] & 1 ? e[no[i]].L : e[no[i]].R);
			if (1 <= pre[i] && pre[i] <= mid) ev = e[no[i]].L;
			ll v = dis[x] + ev;
			if (v < dis[y]) {
				dis[y] = v;
				pq.push(make_pair(-dis[y], y));
			}
		}
	}
	return dis[2] & 1;
}

int main() {
	freopen("travel.in","r",stdin);
	// freopen("travel.out","w",stdout);
	cin >> n >> m >> p;
	for(int i = 1; i <= m; i++) {
		int u = 0, v = 0;
		scanf("%d %d %d %d", &u, &v, &e[i].L, &e[i].R);
		e[i].from = u, e[i].to = v;

		e[i].L *= 2;
		e[i].R *= 2;

		to[++tot] = v, nex[tot] = final[u], final[u] = tot;
		no[tot] = i;
	}

	for(int i = 1; i <= p; i++) {
		scanf("%d", &ed[i]);
		pre[ed[i]] = i;
	}
	int l = 1, r = p, ans = 0;
	while (l <= r) {
		if (ok(l + r>> 1)) {
			ans = l = l + r >> 1;
			l++;
		} else r = (l + r >> 1) - 1;
	}
	if (ans == p) printf("No Response!\n");
	else printf("%d\n", ed[ans + 1]);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值