DP与最短路联系
以 0 − 1 0-1 0−1背包为例
转移方程为
f(i,j)=max(f(i-1,j),f(i-1,j-w)+v)
可以看成图论中的路径;
有以下关系;
这启发我们,如果一个DP问题的依赖关系不具有拓扑序,那么我们可以用最短路算法来解决这种DP不能解决的问题;
比如说像下图这种形成了环路的,用
D
P
DP
DP是没法解决的;
但是我们可以用最短路来解决;
题面
思路
要求一次买入卖出可以得到的最大收益;
我们设定集合为先买后卖的所有方案中的最大值;
将上述集合划分成若干个子集;
分别是以
1
1
1号点,
2
2
2号点,…,
n
n
n号点为买卖分界点的方案;
买卖分界点指的是:在某个点之前买入,在某个点之后卖出,注意买卖都是包含这个点的;
这样我们就可以分成若干个不遗漏的子集(有重复,但是求最值不影响);
只要我们求出每一类的最大值,取一个 m a x max max即可;
对于第 k k k类来说,我们需要求从 [ 1 , k ] [1,k] [1,k]买入的最小值以及求 [ k , n ] [k,n] [k,n]卖出的最大值;
不难发现 [ 1 , k ] [1,k] [1,k]和 [ k , n ] [k,n] [k,n]是独立的;
因此第 k k k类最大值就是 [ k , n ] m a x − [ 1 , k ] m i n [k,n]_{max}-[1,k]_{min} [k,n]max−[1,k]min
由于这种依赖关系不是拓扑序,是可能有环的,因此这里我们不能用 D P DP DP来解决,而需要用最短路来解决;
因此我们只需要正向和反向各跑一次最短路即可;
接着考虑求最短路的算法;
因为是非负权的,我们考虑SPFA
和迪杰斯特拉
;
迪杰斯特拉能不能用的一个关键点在于,从堆中取出一个最小值,断定这个点不再会被其他点更新,那么我们就可以使用;
也就是要使用迪杰斯特拉的前提条件是明确知道起点一定是最短的,因为基于贪心的策略,每次都是使用距离最短的点去更新其他的点,如果不能保证起点是最短的就不能用;
而这道题,由于环的存在再加上这个权值的定义是路径某一点的值;
因此有可能发生出去跑几圈到达最便宜的点又回到起点,这时起点就会又变小,这样我们就不能确保起点最小,那么就寄了;
接着考虑SPFA
算法,而SPFA
本质就是Bellman-ford
算法;
Bellman-ford
一般来说都是正确的;
因为它是基于边数来考虑的,只要最短路径经过的边数是小于等于 n − 1 n-1 n−1的,就一定是正确的;
因为点数为 n n n,边数为 n − 1 n-1 n−1时,就已经包含所有点了;
这是Bellman-ford
的算法流程;
因此这题可以用SPFA
来写;
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 100000 + 10,M = 500000 + 10;
struct Edge{
int next,to,val;
}e[M<<2];//双向边 + 反向图
int tot;
//start_head,tail_head 正向边和反向边
int sh[N],th[N],a[N];
int distmx[N],distmn[N];
bool vis[N];
int n,m;
void add(int head[],int u,int v){
e[++tot].to = v;
e[tot].next = head[u];
head[u] = tot;
}
void into_que(queue<int>&que,int to){
if(vis[to] == 0){
que.push(to);
vis[to] = 1;
}
}
void spfa(int head[],int dist[],int type){
queue<int> que;
memset(vis,0,sizeof vis);
//正向跑
if(type == 1){
memset(dist,0x3f,sizeof distmx);
dist[1] = a[1];
que.push(1);
vis[1] = 1;
}
else{
memset(dist,-0x3f,sizeof distmn);
dist[n] = a[n];
que.push(n);
vis[n] = 1;
}
while(!que.empty()){
int u = que.front();
que.pop();
vis[u] = false;
for(int i=head[u];i;i=e[i].next){
int to = e[i].to;
if(type == 1){ //求最小值
//取自己 或者取来源点
if(dist[to] > min(dist[u],a[to])){
dist[to] = min(dist[u],a[to]);
into_que(que,to);
}
}
else{ //求最大值
if(dist[to] < max(dist[u],a[to])){
dist[to] = max(dist[u],a[to]);
into_que(que,to);
}
}
}
}
}
void solve(){
cin >> n >> m;
for(int i=1;i<=n;++i) cin >> a[i];
while(m--){
int u,v,type;
cin >> u >> v >> type;
add(sh, u, v), add(th, v, u);
if(type == 2) add(sh, v, u), add(th, u, v);
}
spfa(sh,distmn,1);
spfa(th,distmx,2);
//枚举每一类 取一个max
int ans = 0;
for(int i=1;i<=n;++i){
ans = max(ans,distmx[i] - distmn[i]);
}
cout << ans << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}