1. 程式人生 > >洛谷P4009:汽車加油行駛問題

洛谷P4009:汽車加油行駛問題

題目描述

      給定一個 N \times NN×N 的方形網格,設其左上角為起點◎,座標(1,1)(1,1) ,XX 軸向右為正, YY 軸向下為正,每個方格邊長為 11,如圖所示。

      一輛汽車從起點◎出發駛向右下角終點▲,其座標為 (N,N)(N,N) 。

      在若干個網格交叉點處,設定了油庫,可供汽車在行駛途中加油。汽車在行駛過程中應遵守如下規則:

  1. 汽車只能沿網格邊行駛,裝滿油後能行駛 KK 條網格邊。出發時汽車已裝滿油,在起點與終點處不設油庫。

  2. 汽車經過一條網格邊時,若其 XX 座標或 YY 座標減小,則應付費用 BB ,否則免付費用。

  3. 汽車在行駛過程中遇油庫則應加滿油並付加油費用 A

    A 。

  4. 在需要時可在網格點處增設油庫,並付增設油庫費用 CC (不含加油費用AA )。

  5. N,K,A,B,CN,K,A,B,C 均為正整數, 且滿足約束: 2\leq N\leq 100,2 \leq K \leq 102N100,2K10 。

設計一個演算法,求出汽車從起點出發到達終點所付的最小費用。

解法

      一看到這種用網路流解決圖論的題目,我就覺得噁心,構圖會很繁瑣,然後又容易打錯。在比賽中碰上這種題,我肯定會先跳過。。

      不說那麼多,我們來講一下解法。

      1.首先有兩個東西需要我們處理,一個是當前的油量,一個是當前所用的費用,因為這道題的k(油箱最大額度)很小,只有十。所以我們就想到了以當前的油量分層。我們把第0

層設為還可以走k,第1層設為還可以走k-1......那麼第k層就設為還可以走0步。如下圖所示

      我認真了。。】

      2.建邊詳情會在程式碼裡面說,(實在是太長了。。)

      3.建完邊之後,我們就按照費用流來跑SPFA,然後依次更新路徑上的點,最後返回cost的和就好

(好咯,貼程式碼。。)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<queue>
using namespace std;

int n,k,a,b,c;
struct edge{int x,y,next,cos,c;};
edge s[1000010];
int begin,end;
int every;
int len=1;
int first[110010];
int h[110010];
bool visit[110010];
int mmin[110010];
int fa[110010];

void ins(int x,int y,int c,int cos)
{
	len++;
	s[len].x=x;s[len].y=y;s[len].c=c;s[len].cos=cos;
	s[len].next=first[x];first[x]=len;
	len++;
	s[len].x=y;s[len].y=x;s[len].c=0;s[len].cos=-cos;
	s[len].next=first[y];first[y]=len;
}

bool SPFA(int &cost,int &flow)
{
	queue<int> f;
	f.push(begin);
	memset(h,63,sizeof(h));
	memset(visit,false,sizeof(visit));
	mmin[begin]=1000000000;
	h[begin]=0;
	visit[begin]=true;
	while(!f.empty())
	{
		int x=f.front();
		f.pop();
		visit[x]=false;
		for(int i=first[x];i!=0;i=s[i].next)
		{
			int y=s[i].y;
			if(h[y]>h[x]+s[i].cos && s[i].c>0)
			{
				fa[y]=i;
				mmin[y]=min(mmin[x],s[i].c);
				h[y]=h[x]+s[i].cos;
				if(visit[y]==false) f.push(y);
				visit[y]=true;
			}
		}
	}
	if(h[end]==1061109567) return false;
	flow+=mmin[end];
	cost+=mmin[end]*h[end];
	int now=end;
	while(now!=begin)
	{
		int i=fa[now];
		s[i].c-=mmin[end];
		s[i^1].c+=mmin[end];
		now=s[i].x;
	}
	return true;
}

int Cost_Flow()
{
	int cost=0,flow=0;
	while(SPFA(cost,flow));
	return cost;
}

int main()
{
	scanf("%d %d %d %d %d",&n,&k,&a,&b,&c);
	every=10000;
	begin=0;end=110001;
	ins(begin,1,1,0);
	for(int cent=0;cent<=k;cent++) ins(cent*every+n*n,end,1,0);\\把圖分完層之後,每張圖的(n,n)這個點都向匯點連一條邊。
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		{
			int x;
			scanf("%d",&x);
			int pos=n*(i-1)+j;
			if(x==1)
				for(int cent=k-1;cent>=0;cent--)
				{
					if(i>1) ins(cent*every+pos-n,pos,1,a);//這裡的操作,相當於不同層的pos附近的
					if(j>1) ins(cent*every+pos-1,pos,1,a);//點向pos連一條a的邊,當然,逆向走要
					if(i<n) ins(cent*every+pos+n,pos,1,a+b);//加b。(就是說到pos這個點的時候
					if(j<n) ins(cent*every+pos+1,pos,1,a+b);//滿血復活了,必須要加油,花費a。)
				}
			else
			{
				for(int cent=k;cent>0;cent--) ins(cent*every+pos,pos,1,a+c);//可以選擇在pos建一個
				for(int cent=0;cent<=k-1;cent++)//加油站,花費為c,加滿花費為a,總共a+c。
				{
					if(i>1) ins(cent*every+pos-n,(cent+1)*every+pos,1,0);//否則就到下一層相對應
					if(j>1) ins(cent*every+pos-1,(cent+1)*every+pos,1,0);//的位置上,費用為0,
					if(i<n) ins(cent*every+pos+n,(cent+1)*every+pos,1,b);//逆向走要加費用b,到
					if(j<n) ins(cent*every+pos+1,(cent+1)*every+pos,1,b);//下一層意味著,油減少1
				}
			}
		}
	printf("%d",Cost_Flow());
}//記得陣列開大一點哦!

喜歡小編的關注我哦!Tarjan會特別照料你的。偷笑偷笑微笑