题目
C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 m 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为 1 条。
C 国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。
商人阿龙来到 C 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 C 国 n 个城市的标号从 1~ n,阿龙决定从 1 号城市出发,并最终在 n 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 n 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 C 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。
假设 C 国有 5 个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。
题解1
2*spfa
仔细观察题目可以发现,一个城市能是买入点,那这个城市一定能从1出发到达。一个城市能是卖出点,那么这个城市一定能到达n。
有了这么一个性质再来想,买入点要尽可能的小,那么我们是不是可以写一个DP方程:,y为所有能到达x的节点。
由于是无向边且存在环,存在后效性,所以不能直接DP。根据spfa的收敛的特性,我们可以把DP方程与spfa结合起来,让spfa帮我们解决后效性问题。(还有一类解决后效性的方法还有高斯消元)
我们得到一个算法,从起点出发用spfa求出每个点的最小买入价;从终点出发用spfa求每个点的最大卖出价。,特别注意到公式中的vis[i]=true,意思是这个点能由1到达且能去到n。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
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]<d2[x])//注意此处DP方程
{
d2[y]=d2[x];
if(!v[y])
{
q[tail++]=y;if(tail==100005) tail=0;
v[y]=true;
}
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
ins(x,y);
if(z==2) ins(y,x);
}
spfa1();
spfa2();
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,d2[i]-d1[i]);
printf("%d\n",ans);
return 0;
}
题解2
建分层图,跑spfa
这篇博客是我分层图的启蒙老师
我们建3层的图,分别表示3种状态:
第一层:寻找买入点的时候
第二层:已经买入了水晶球,找地方卖出的时候
第三层:卖完了,走向终点的时候
每次建边时,不仅要在当前这一层连边,还要往上一层连边,已完成一个连贯的图。
其中在当前层建边时边权为0,第一层上第二层的边权为-w[i](买入),第二层上第三层的边权为w[i](卖出)。
在建一个超级终点连接第一层和第三层的n,表示结束的时候。
在以上基础上跑一遍最长路即可得解。
小结
分层图的建设,使得这题中的三种情况合三为一,可以直接用一种算法求解。该算法适合有多种情况且这些情况有不可分关系的spfa使用。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
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]<d[x]+a[k].c)
{
d[y]=d[x]+a[k].c;
if(v[y]==false)
{
v[y]=true;
list[tail++]=y;if(tail==n3) tail=0;
}
}
}
}
}
int main()
{
scanf("%d%d", &n, &m);
n2=2*n;n3=3*n;
for(int i=1;i<=n;i++) scanf("%d", &w[i]);
ins(n,n3+1,0);ins(n3,n3+1,0);
for(int i=1;i<=m;i++)
{
int x,y,z;
scanf("%d%d%d", &x, &y, &z);
ins(x,y,0);
ins(x+n,y+n,0);
ins(x+n2,y+n2,0);
ins(x,y+n,-w[y]);
ins(x+n,y+n2,w[y]);
if(z==2)
{
ins(y,x,0);
ins(y+n,x+n,0);
ins(y+n2,x+n2,0);
ins(y,x+n,-w[x]);
ins(y+n,x+n2,w[x]);
}
}
spfa(1);
printf("%d\n",d[n3+1]);
return 0;
}