P1073 最优贸易(SPFA)

最优贸易

NOIP 2009 提高组 第三题

题目大意
有n个点,有m个通道(有向或无向)。他要找到一条路,点可以重复出现。在路上,买水晶球再卖掉水晶球,最后到n点。要求出他最大能赚多少钱(用卖水晶球的钱减买水晶球的钱
分析
复杂的分析
在这里插入图片描述下面深入理解
可以用两个spfa算法
邻接表建立,如图:
在这里插入图片描述从1点去找,找到能到的点,并且赋值给他们:买水晶球最小的钱

void spfa1(int o)
{
	minn[o]=w[o];//初值
    head=0;tail=1;
	b[1]=o;c[o]=1;
    do
    {
        head=head%N+1;//循环队列,否则会炸
        int k=b[head];//方便
        for(int i=head1[k];i;i=a1[i].next)
         if(minn[a1[i].to]>min(minn[k],w[a1[i].to]))//松弛算法
          {
            minn[a1[i].to]=min(minn[k],w[a1[i].to]);
            if(c[a1[i].to]==0)//是否标记过,没有就将这个数进入队列
             {
            	tail++;
            	b[tail]=a1[i].to;
            	c[a1[i].to]=1;
             }
          }
    	c[k]=0;
    }while(head<tail);
}

下面的需要将邻接表倒置,如图
在这里插入图片描述
从n点去找,找到能到的点,并且赋值给他们:卖水晶球最大的钱

void spfa2(int o)
{
	maxn[o]=w[o];//初值
    head=0;tail=1;
	b[1]=o;c[o]=1;
    do
    {
        head=head%N+1;//循环队列,否则炸
        int k=b[head];//方便
        for(int i=head2[k];i;i=a2[i].next)
         if(maxn[a2[i].to]<max(maxn[k],w[a2[i].to]))//松弛算法(注意符号)
          {
            maxn[a2[i].to]=max(maxn[k],w[a2[i].to]);
            if(c[a2[i].to]==0)//进入队列或不进入队列
             {
            	tail++;
            	b[tail]=a2[i].to;
            	c[a2[i].to]=1;
             }
          }
    	c[k]=0;
    }while(head<tail);
}

注意:复制后要仔细修改一些细节
最后,从2点到(n-1)点进行枚举,找到同一个点,让它的卖水晶球减卖水晶球的钱最多(不包括起点和终点)

for(int i=2;i<=n-1;i++)//找最大赚的钱
	 m=max(m,maxn[i]-minn[i]);

这个点得出的值就是结果
AC代码

#include<iostream>
using namespace std;
#define N 1000005
#define M 5000005
int n,m,x,y,z,tot1,tot2,head,tail,b[M],c[N],w[N],head1[M],head2[M],maxn[N],minn[N];
struct stu
{
	int to,next,w;
}a1[M],a2[M];
void add1(int x,int y)//邻接表1号
{
	tot1++;
	a1[tot1].to=y;
	a1[tot1].next=head1[x];
	head1[x]=tot1;
}
void add2(int x,int y)//邻接表2号(要倒置)
{
	tot2++;
	a2[tot2].to=y;
	a2[tot2].next=head2[x];
	head2[x]=tot2;
}
void spfa1(int o)
{
	minn[o]=w[o];//初值
    head=0;tail=1;
	b[1]=o;c[o]=1;
    do
    {
        head=head%N+1;//循环队列,否则会炸
        int k=b[head];//方便
        for(int i=head1[k];i;i=a1[i].next)
         if(minn[a1[i].to]>min(minn[k],w[a1[i].to]))//松弛算法
          {
            minn[a1[i].to]=min(minn[k],w[a1[i].to]);
            if(c[a1[i].to]==0)//是否标记过,没有就将这个数进入队列
             {
            	tail++;
            	b[tail]=a1[i].to;
            	c[a1[i].to]=1;
             }
          }
    	c[k]=0;
    }while(head<tail);
}
void spfa2(int o)
{
	maxn[o]=w[o];//初值
    head=0;tail=1;
	b[1]=o;c[o]=1;
    do
    {
        head=head%N+1;//循环队列,否则炸
        int k=b[head];//方便
        for(int i=head2[k];i;i=a2[i].next)
         if(maxn[a2[i].to]<max(maxn[k],w[a2[i].to]))//松弛算法(注意符号)
          {
            maxn[a2[i].to]=max(maxn[k],w[a2[i].to]);
            if(c[a2[i].to]==0)//进入队列或不进入队列
             {
            	tail++;
            	b[tail]=a2[i].to;
            	c[a2[i].to]=1;
             }
          }
    	c[k]=0;
    }while(head<tail);
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>w[i];//每个地方的买和卖水晶球的钱
		minn[i]=2147483647;//赋大值
	}
	for(int i=1;i<=m;i++)
	{
		cin>>x>>y>>z;
		add1(x,y);
		add2(y,x);
		if(z==2)add1(y,x);//双向
		if(z==2)add2(x,y);//双向
	}
	spfa1(1);
	for(int i=1;i<=n;i++)//清零
	 c[i]=0;
	spfa2(n);
	m=0;
	for(int i=2;i<=n-1;i++)//找最大赚的钱
	 m=max(m,maxn[i]-minn[i]);
	cout<<m;
	return 0;
} 

谢谢

发布了49 篇原创文章 · 获赞 79 · 访问量 1632

猜你喜欢

转载自blog.csdn.net/weixin_45524309/article/details/104011856