洛谷1073 最优贸易(spfa)|(分层图)

题目

C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。

C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 n 个城市的标号从 1~ n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

假设 C 国有 5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

题解1

2*spfa
仔细观察题目可以发现,一个城市能是买入点,那这个城市一定能从1出发到达。一个城市能是卖出点,那么这个城市一定能到达n。
有了这么一个性质再来想,买入点要尽可能的小,那么我们是不是可以写一个DP方程:f[x]=\min \left\{ f[y] \right\},y为所有能到达x的节点。
由于是无向边且存在环,存在后效性,所以不能直接DP。根据spfa的收敛的特性,我们可以把DP方程与spfa结合起来,让spfa帮我们解决后效性问题。(还有一类解决后效性的方法还有高斯消元)
我们得到一个算法,从起点出发用spfa求出每个点的最小买入价;从终点出发用spfa求每个点的最大卖出价。ans=\max_{vis[i]=true} \left\{sold[i]-buy[i]\right\},特别注意到公式中的vis[i]=true,意思是这个点能由1到达且能去到n。

代码

#include
#include
#include
using namespace std;
const int maxn=100010,maxm=500000;

int n,m;
int a[maxn];

struct E{int x,y,next;}e1[maxm<<1],e2[maxm<<1];int len=0,last1[maxn],last2[maxn];
void ins(int x,int y)
{
    len++;
    e1[len]=(E){x,y,last1[x]};last1[x]=len;
    e2[len]=(E){y,x,last2[y]};last2[y]=len;
}

int q[maxn];int head,tail;bool v[maxn];
bool vis[maxn];

int d1[maxn];
void spfa1()//从起点出发求最小买入价 
{
    head=0;tail=1;
    q[0]=1;
    memset(d1,63,sizeof(d1));d1[1]=a[1];
    memset(vis,false,sizeof(vis));vis[1]=true;
    while(head!=tail)
    {
        int x=q[head++];if(head==100005) head=0;
        v[x]=false;
        for(int k=last1[x];k;k=e1[k].next)
        {
            int y=e1[k].y;
            if(!vis[y])//debug 部分点与1、n不连通,此时不应该有自己的a[i]
            {
                vis[y]=true;
                q[tail++]=y;if(tail==100005) tail=0;
                v[y]=true;
                d1[y]=a[y];
            }
            if(d1[y]>d1[x])//注意此处DP方程 
            {
                d1[y]=d1[x];
                if(!v[y])
                {
                    q[tail++]=y;if(tail==100005) tail=0;
                    v[y]=true;
                }
            }
        }
    }
}

int d2[maxn];
void spfa2()//从终点出发求最大卖出价 
{
    head=0;tail=1;
    q[0]=n;
    memset(d2,190,sizeof(d2));d2[n]=a[n];
    memset(vis,false,sizeof(vis));vis[n]=true;
    while(head!=tail)
    {
        int x=q[head++];if(head==100005) head=0;
        v[x]=false;
        for(int k=last2[x];k;k=e2[k].next)
        {
            int y=e2[k].y;
            if(!vis[y])//debug 部分点与1、n不连通,此时不应该有自己的a[i]
            {
                vis[y]=true;
                q[tail++]=y;if(tail==100005) tail=0;
                v[y]=true;
                d2[y]=a[y];
            }
            if(d2[y]

 

题解2

建分层图,跑spfa

 

这篇博客是我分层图的启蒙老师

我们建3层的图,分别表示3种状态:
第一层:寻找买入点的时候
第二层:已经买入了水晶球,找地方卖出的时候
第三层:卖完了,走向终点的时候

每次建边时,不仅要在当前这一层连边,还要往上一层连边,已完成一个连贯的图。
其中在当前层建边时边权为0,第一层上第二层的边权为-w[i](买入),第二层上第三层的边权为w[i](卖出)。
在建一个超级终点连接第一层和第三层的n,表示结束的时候。

在以上基础上跑一遍最长路即可得解。

 

小结

分层图的建设,使得这题中的三种情况合三为一,可以直接用一种算法求解。该算法适合有多种情况且这些情况有不可分关系的spfa使用。

代码

#include
#include
#include
using namespace std;
const int maxn=100010;
const int maxm=500010;


int n,n2,n3,m;
int w[maxn];


struct node
{
	int x,y,c,next;
}a[maxm*2*5];int len=0,last[maxn*3];
void ins(int x,int y,int c)
{
	len++;
	a[len].x=x;a[len].y=y;a[len].c=c;
	a[len].next=last[x];last[x]=len;
}


int list[maxn*3];bool v[maxn*3];
int d[maxn*3];
void spfa(int st)
{
	int head=0,tail=1;
	list[0]=st;v[st]=true;
	memset(d,128,sizeof(d));d[st]=0;
	while(head!=tail)
	{
		int x=list[head++];if(head==n3) head=0;
		v[x]=false;
		for(int k=last[x];k;k=a[k].next)
		{
			int y=a[k].y;
			if(d[y]

 

 

你可能感兴趣的:(刷题之路,动态规划DP,SPFA)