P1073 [NOIP2009 提高组] 最优贸易 题解
各位大佬都用了分层图,但却只用了一种写法,蒟蒻过来补充另外一种写法。
思路
对于这一类问题,我们考虑分层图。
何为分层图?即将一张图分成好多层,同时在这么多张图中连边,然后通过一些算法来实现解题的目的。当题目中的边权会改变或者有特殊边(比如有 k k k 次机会边权变为 0 0 0)时就有分层图的用武之地了。
对于这道题目,因为它给的是点权,所以我们首先想到把点权转边权。即将 u u u 到 v v v 的路径的边权赋为 v v v 节点的点权。
随后,容易发现在购买水晶球时,因为要花钱,所以边权为负。同时,在卖出水晶球时,边权又为正了。边权会改变这不就符合了使用分层图的特点吗!
所以,我们可以建 3 3 3 层图。哪 3 3 3 层?
- 第一层表示还没有买水晶球。
- 第二层表示买了水晶球,但没有卖出去。
- 第三层表示已经卖出了水晶球。
建好了图,现在我们考虑连边:
- 不难想到对于同一层的节点,相连的边权应为 0 0 0。
- 第一层的节点到第二层的节点,因为要买水晶球,所以边权应为 − w -w −w,即边权的相反数。
- 第二层的节点到第三层的节点,因为已经卖出了水晶球,所以边权就是 w w w。
答案就是第一层中 1 1 1 号节点到第三层中 n n n 号节点的最长路。
代码实现
分层图
为了实现分层图的多层,我们通常有两种操作:
-
在连边时实现层与层之间相连。各位大佬都讲得很好了,我这里重点给大家讲第二种。
-
正常连边,然后把最短路数组和 bool 数组都开成二维: d i s [ n ] [ l a y e r ] dis[n][layer] dis[n][layer] 与 f l a g [ n ] [ l a y e r ] flag[n][layer] flag[n][layer]。其中 n n n 是节点数量, l a y e r layer layer 是层数。往 spfa 的队列加一个 p a i r pair pair 保存层数。经过这两个操作,多层的图就被存放在了数组里。然后,我们在跑最长路(或最短路)时就可以对其进行转移,即层与层之间的转移。需要注意的是,转移需要考虑是否已经是顶层,同时,同一层之间要记得松弛。
注意事项
-
对于这道题目,由于第一层到第二层是负边权,所以我们需要特殊处理。
-
因为可以在自己所在的节点上买卖水晶球,所以要在同一个节点上连边(自环)。
-
要跑最长路,数组记得要赋极小值。
-
不要忘了,在答案小于 0 0 0 时输出 0 0 0。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN = 1e5 + 10 , MAXM = 1e6 + 10;
int n , m;
int a[MAXN];
int he[MAXN] , to[MAXM] , ne[MAXM] , w[MAXM];
int idx;
int dis[MAXN][5];
bool flag[MAXN][5];
int cnt;
inline void add(int a , int b , int c) {
to[++ idx] = b;
w[idx] = c;
ne[idx] = he[a];
he[a] = idx;
return;
}
inline void spfa() {
queue<pair<int , int> >q;
q.push(make_pair(1 , 1));
memset(dis , 0xcf , sizeof(dis));
dis[1][1] = 0;
while(!q.empty()) {
int u = q.front().first;
int layer = q.front().second;
q.pop();
flag[u][layer] = false;
for(register int i = he[u];i;i = ne[i]) {
int v = to[i];
if(layer == 1) {
if(dis[v][layer + 1] < dis[u][layer] - w[i]) {
dis[v][layer + 1] = dis[u][layer] - w[i];
if(!flag[v][layer + 1]) {
flag[v][layer + 1] = true;
q.push(make_pair(v , 2));
}
}
if(dis[v][layer] < dis[u][layer]) {
dis[v][layer] = dis[u][layer];
if(!flag[v][layer]) {
flag[v][layer] = true;
q.push(make_pair(v , 1));
}
}
} else {
if(layer + 1 <= 3 && dis[v][layer + 1] < dis[u][layer] + w[i]) {
dis[v][layer + 1] = dis[u][layer] + w[i];
if(!flag[v][layer + 1]) {
flag[v][layer + 1] = true;
q.push(make_pair(v , layer + 1));
}
}
if(dis[v][layer] < dis[u][layer]) {
dis[v][layer] = dis[u][layer];
if(!flag[v][layer]) {
flag[v][layer] = true;
q.push(make_pair(v , layer));
}
}
}
}
}
return;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
for(register int i = 1;i <= n;i ++) {
cin >> a[i];
add(i , i , a[i]);
}
while(m --) {
int x , y , z;
cin >> x >> y >> z;
if(z == 1)
add(x , y , a[y]);
else {
add(x , y , a[y]);
add(y , x , a[x]);
}
}
spfa();
if(dis[n][3] > 0)
cout << dis[n][3];
else
cout << 0;
return 0;
}