题解:P1073 [NOIP2009 提高组] 最优贸易

P1073 [NOIP2009 提高组] 最优贸易 题解

各位大佬都用了分层图,但却只用了一种写法,蒟蒻过来补充另外一种写法。

思路

对于这一类问题,我们考虑分层图。

何为分层图?即将一张图分成好多层,同时在这么多张图中连边,然后通过一些算法来实现解题的目的。当题目中的边权会改变或者有特殊边(比如有 k k k 次机会边权变为 0 0 0)时就有分层图的用武之地了。

对于这道题目,因为它给的是点权,所以我们首先想到把点权转边权。即将 u u u v v v 的路径的边权赋为 v v v 节点的点权。

随后,容易发现在购买水晶球时,因为要花钱,所以边权为负。同时,在卖出水晶球时,边权又为正了。边权会改变这不就符合了使用分层图的特点吗!

所以,我们可以建 3 3 3 层图。哪 3 3 3 层?

  1. 第一层表示还没有买水晶球。
  2. 第二层表示买了水晶球,但没有卖出去。
  3. 第三层表示已经卖出了水晶球。

建好了图,现在我们考虑连边:

  • 不难想到对于同一层的节点,相连的边权应为 0 0 0
  • 第一层的节点到第二层的节点,因为要买水晶球,所以边权应为 − w -w w,即边权的相反数。
  • 第二层的节点到第三层的节点,因为已经卖出了水晶球,所以边权就是 w w w

答案就是第一层中 1 1 1 号节点到第三层中 n n n 号节点的最长路。

代码实现

分层图

为了实现分层图的多层,我们通常有两种操作:

  1. 在连边时实现层与层之间相连。各位大佬都讲得很好了,我这里重点给大家讲第二种。

  2. 正常连边,然后把最短路数组和 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值