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+(disi−disj)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+(disi−disj)2>fk+(disi−disk)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
fj−2disidisj+disj2>fk−2disidisk+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)>2disidisj−2disidisk
设
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)
Yj−Yk>disi(Xj−Xk)
Y
j
−
Y
k
X
j
−
X
k
>
d
i
s
i
\dfrac{Y_j-Y_k}{X_j-X_k}>dis_i
Xj−XkYj−Yk>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;
}