【jzoj 7181】【LOJ 2769】City / 前往大都会(斜率优化DP)

本文探讨了一种特殊的最短路径问题,除了寻找两点间最短时间外,还需最大化路径上时间差平方和。通过使用优先队列实现最短路径算法,并结合斜率优化DP策略,实现了高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

City / 前往大都会

题目链接:jzoj 7181 / LOJ 2769

题目大意

有一些单向铁路,然后每段有起点终点时间。
然后你要从 1 号点用最短时间到 n 号点,并且要你连续走同一条铁路的时间的平方和的和最大。

(算了,我实在说不清楚了,直接把题目给你们看吧)
在这里插入图片描述

思路

不难想到第一个答案就是一个最短路的事情,接着我们考虑怎么搞第二个答案。
考虑把最短路径搞出来,然后 DP。
(其实在这里不用拓扑出来,你按 d i s dis dis 从小到大枚举搞就可以了)

然后考虑怎么 DP,对于它所在的一段铁路,有这样的一个转移:
f i = max ⁡ { f i , f j + ( d i s i − d i s j ) 2 } f_i=\max\{f_i,f_j+(dis_i-dis_j)^2\} fi=max{fi,fj+(disidisj)2}
(要在同一条铁路上)

然后不难看到它是斜率优化,然后来搞:( j j j k k k 优)
f j + ( d i s i − d i s j ) 2 > f k + ( d i s i − d i s k ) 2 f_j+(dis_i-dis_j)^2>f_k+(dis_i-dis_k)^2 fj+(disidisj)2>fk+(disidisk)2
f j − 2 d i s i d i s j + d i s j 2 > f k − 2 d i s i d i s k + d i s k 2 f_j-2dis_idis_j+dis_j^2>f_k-2dis_idis_k+dis_k^2 fj2disidisj+disj2>fk2disidisk+disk2
( f j + d i s j 2 ) − ( f k + d i s k 2 ) > 2 d i s i d i s j − 2 d i s i d i s k (f_j+dis_j^2)-(f_k+dis_k^2)>2dis_idis_j-2dis_idis_k (fj+disj2)(fk+disk2)>2disidisj2disidisk
Y i = f i + d i s i 2 , X i = 2 d i s i Y_i=f_i+dis_i^2,X_i=2dis_i Yi=fi+disi2,Xi=2disi
Y j − Y k > d i s i ( X j − X k ) Y_j-Y_k>dis_i(X_j-X_k) YjYk>disi(XjXk)
Y j − Y k X j − X k > d i s i \dfrac{Y_j-Y_k}{X_j-X_k}>dis_i XjXkYjYk>disi

然后就转移就可以了。
然后因为它要在同一铁路上,所以你要记录一个点在某个铁路中应该放到哪个栈,然后就每个铁路维护一个栈。

代码

#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define ll long long

using namespace std;

struct node {
	int x, to, nxt, op;
}e[1000001];
priority_queue <pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
int n, m, s[1000001], le[1000001], KK, tot;
int x, y, z, dis[1000001], ans, id[10000001];
bool in[1000001];
ll f[1000001], X[1000001], Y[1000001];
vector <int> st[2000001], whst[1000001];
vector <pair<int, int> > rd[1000001], pl[1000001];

void add(int x, int y, int z, int op) {
	e[++KK] = (node){z, y, le[x], op}; le[x] = KK;
}

void dij() {//求最短路(SPFA会爆)
	memset(dis, 0x7f, sizeof(dis));
	dis[1] = 0;
	q.push(make_pair(0, 1));
	while (!q.empty()) {
		int now = q.top().second;
		q.pop();
		if (in[now]) continue;
		
		in[now] = 1;
		for (int i = le[now]; i; i = e[i].nxt)
			if (dis[e[i].to] > dis[now] + e[i].x) {
				dis[e[i].to] = dis[now] + e[i].x;
				q.push(make_pair(dis[e[i].to], e[i].to));
			}
	}
}

bool cmp(int x, int y) {
	return dis[x] < dis[y];
}

double slope(int x, int y) {
	return 1.0 * (Y[x] - Y[y]) / (X[x] - X[y]);
}

//斜率优化DP
void slove() {
	for (int i = 1; i <= n; i++) id[i] = i;
	sort(id + 1, id + n + 1, cmp);
	
	for (int i = 1; i <= n; i++) {
		int now = id[i];
		for (int j = 0; j < pl[now].size(); j++) {
			int x = pl[now][j].first, y = pl[now][j].second;
			if (y && dis[rd[x][y - 1].first] + rd[x][y - 1].second == dis[now]) whst[x][y] = whst[x][y - 1];
				else whst[x][y] = ++tot;//看下一个是用哪个栈
			int p = whst[x][y];
			while (st[p].size() >= 2 && slope(st[p][st[p].size() - 1], st[p][st[p].size() - 2]) < 1.0 * dis[now])
				st[p].pop_back();
			if (st[p].size()) {
				int j = st[p].back();
				f[now] = max(f[now], f[j] + 1ll * (dis[now] - dis[j]) * (dis[now] - dis[j]));
			}
		}
		
		X[now] = 2ll * dis[now];
		Y[now] = f[now] + 1ll * dis[now] * dis[now];
		
		for (int j = 0; j < pl[now].size(); j++) {
			int x = pl[now][j].first, y = pl[now][j].second, p = whst[x][y];
			while (st[p].size() >= 2 && slope(st[p][st[p].size() - 1], st[p][st[p].size() - 2]) < slope(now, st[p][st[p].size() - 1]))
				st[p].pop_back();
			st[p].push_back(now);
		}
	}
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d", &s[i]);
		scanf("%d", &x);
		pl[x].push_back(make_pair(i, 0));
		for (int j = 1; j <= s[i]; j++) {
			scanf("%d", &z);
			scanf("%d", &y);
			add(x, y, z, i);
			rd[i].push_back(make_pair(x, z));//这个放的是道路,两个分别是起点和路径长度
			pl[y].push_back(make_pair(i, j));//这个放的是点,两个分别是它有哪条铁路,是铁路的第几段
			whst[i].push_back(0);
			x = y;
		}
		rd[i].push_back(make_pair(x, 0));
		whst[i].push_back(0);
	}
	
	dij();
	
	slove();
	
	printf("%d %lld", dis[n], f[n]);
	
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值