题解 P1073 【最优贸易】

首先,SPFA大法没死!!!

步入正题

开始

因为我刚学完最短路没多久(所以这应该会是一篇对新手比较友好的题解QAQ),这题涉及到了费用问题所以可以考虑最短路,但是我只会Floyd,dijkstra还有SPFA,但是一想到买和卖,当然是一个正数一个负数,于是果断选择SPFA(课上说一有负权边就用SPFA,QAQ)

核心思想

引入

课上讲了这道题的大概轮廓,核心思想是分层图

通俗来讲就是把不同的状态不同层的图来表达感觉其实点都不通俗,举个例子,一道简单的求最短路的题目,它硬是加上了某些花里胡哨的条件,让你不能那么顺利的求最短路,这时就可以将限制条件转变成某种状态并单独作为一层图

就比如这题,本来可以是一个旅者找前往某个城市的最短路劲,但是它又加上了balabala水晶球贸易水晶球什么的,还要你求最大收益。

可能可能很容易想到建一张图,用城市与城市之间的权值来表示,但试问如何用一条边来选择购买,出售,还是直接路过呢?

假如旅行者一开始有一个面包,沿途经过某个城市需要吃一定量的面包,这时上述的做法就很方便了,将吃的面包量作为权值求最短路。

但是这里则不然,这时分层图的优势就体现出来了——将不同状态分别作为一张图。

实操

首先我用一组乱打的简单的数据来举例

3 3
3 4 5
1 2 1
1 3 1
2 3 1

建图(邻接表)

关于层

因为题目说只会买卖一次,所以我们可以将什么都没有作为一层,购买了水晶球作为一层,出售了水晶球作为一层,再考虑到是先购买再出售,我们可以将它们向下分别作为1,2,3层(顺序不重要)。

如下

我们将跨层的操作对应到状态的变化或转移,也就是从第一层到第二层就是购买,第二层到第三层就是出售。

代码实现
void ins_self(int node,int val){
	G[node].push_back(make_edge(node+n,-val));
	G[node+n].push_back(make_edge(node+2*n,val));
}

for (int i = 1; i <= n; i++) {
	cin>> v[i];
	ins_self(i,v[i]);
}
//在一开始输入数据的时候就可以完成层之间的连通
//G[N]是用于存图的,n是城市的数量,v[i]是不同城市的标价
//make_edge函数是将某条边的终点和边权打包成结构体以方便存入G[N]中
//N是某个常数

关于边

实现的方式还是利用边的权值,我们在层与层之间连边,对应的权值就是到某个城市购买或出售的费用。

例如我要在一号城市购买,那么只需要从第一层的一号点走向第二层的一号点,用某种手段(一个记录点i到起点距离的数组dis[ i ])来记录权值的变化。另外由于移动不需要费用,所以层内部边之间的边权都是0。

代码实现
void ins(int node,int next,int val) {
	G[node].push_back(make_edge(next,val));
	G[node+n].push_back(make_edge(next+n,val));
	G[node+2*n].push_back(make_edge(next+2*n,val));
}
//层内部的点之间的建边
for (int i = 1; i <= l; i++) {
	cin>> a>> b>> c;
	if (c == 1)
		ins(a,b,0); 
	else if (c == 2) {
		ins(a,b,0);
		ins(b,a,0);
	}
}
// l 是道路的数量,也就是边的数量

关于点

在这里并不需要真的去建三张图,而是给不同层点以不同的编号,直接举例就很容易明白的了

每一层图的节点编号就是它上一层对应的点的编号加上点的个数。

SPFA的实现

很明显我们希望在从第一层到第二层的时候边权能尽量小,第二到第三层的边权能尽量大,那这样我们就需要求出1号点到9号点的最长路就好了!

可是我没学过最长路QAQ(后来发现最长路好像更简单),于是,重点!精髓!我开了个脑洞,能否把买当作卖,把卖当作买,把一开始没有变成一开始有,目的是在途中以廉价卖掉,最后高价购买,这样原来赚的钱最多,反过来之后不久变成了亏的钱最多吗!比如我的例子中显然最赚是,1点买3点卖,赚两块钱,要是反过来,1点卖,3点买,那么就是亏了两块,正好就是原正解的相反数!

这样就完美的解决了最短路的问题,就可以直接把模板复制上去了,这里附上我的模板

代码实现

queue<int> Q;
int spfa(int ed){
	Q.push(1);
	inq[1] = true;
	memset(dis,0x3f,sizeof(dis));
	dis[1] = 0;
	while (!Q.empty()){
		int now = Q.front();
		Q.pop();
		inq[now] = false;
		for (int i = 0;i < G[now].size();i++){
			edge e = G[now][i];
			if (dis[e.next]>dis[now]+e.val){
				dis[e.next] = dis[now] + e.val;
				if (inq[e.next] == false) {
					Q.push(e.next);
					inq[e.next] = true;
				} 
			}
		} 
	} 
	return dis[ed]==INF ? 0 : dis[ed];
}

最后

最后输出spfa(3*n),也就是1号城市经过购买和出售两步操作后到n号城市(对应第三层的3n号)的结果的的相反数就好了!!!没了!!!

结尾

用了这么一种诡异的做法还是有点心虚的,看到这么多dalao的神奇做法,还是打算学习一下,改进改进,希望这篇题解能帮到一些像我一样刚学的小白QAQ,最后我的代码附上(时间和空间好像都不是很好的亚子)。

#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 210011;//这里不知道为什么不能是200011,有dalao看到求教教怎么定数组大小QAQ
const int INF = 0x3f3f3f3f;
int n,l,v[N];//节点,边数 ,权值
int dis[N];
bool inq[N];
struct edge {
	int next,val;
};
vector<edge> G[N];
edge make_edge(int next, int val) {
	edge cum;
	cum.next=next;
	cum.val=val;
	return cum;
}
void ins(int node,int next,int val) {
	G[node].push_back(make_edge(next,0));
	G[node+n].push_back(make_edge(next+n,0));
	G[node+2*n].push_back(make_edge(next+2*n,0));
}
void ins_self(int node,int val){
	G[node].push_back(make_edge(node+n,val));
	G[node+n].push_back(make_edge(node+2*n,-val));
}
queue<int> Q;
int spfa(int ed){
	Q.push(1);
	inq[1] = true;
	memset(dis,0x3f,sizeof(dis));
	dis[1] = 0;
	while (!Q.empty()){
		int now = Q.front();
		Q.pop();
		inq[now] = false;
		for (int i = 0;i < G[now].size();i++){
			edge e = G[now][i];
			if (dis[e.next]>dis[now]+e.val){
				dis[e.next] = dis[now] + e.val;
				if (inq[e.next] == false) {
					Q.push(e.next);
					inq[e.next] = true;
				} 
			}
		} 
	} 
	return dis[ed]==INF ? 0 : dis[ed];
}
int main(void) {
	int a,b,c;
	cin>> n>> l;
	for (int i = 1; i <= n; i++) {
		cin>> v[i];
		ins_self(i,v[i]);
	}//自己建自己(层与层)
	for (int i = 1; i <= l; i++) {
		cin>> a>> b>> c;
		if (c == 1) {
			ins(a,b,0);
		} 
		else if (c == 2) {
			ins(a,b,0);
			ins(b,a,0);
		}//自己建别人(层内部)
	}
	spfa(3*n);
	cout << -dis[n*3]<<endl;
	return 0;
}

祝大家AC IOI!

posted @ 2021-11-01 17:11  tsrigo  阅读(29)  评论(0编辑  收藏  举报