1. 程式人生 > 實用技巧 >spoj703 Mobile Service---線性dp+狀態簡化

spoj703 Mobile Service---線性dp+狀態簡化

題目連結:https://vjudge.net/problem/SPOJ-SERVICE

好題。首先可以考慮設計一個思維狀態f[i][x][y][z]表示完成第i個,三個人分別位於x,y,z位置的最小費用,但是這樣dp的時間複雜度是O(nL^3)的。注意到完成第i個,一定有一個人的位置在Pi。於是可以只用剩下兩人的位置來定義狀態:f[i][j][k]表示完成第i個,除了位置在Pi的人以外,其他兩人的位置在j和k的最小費用,從階段i-1到階段i的狀態間,3個人都可以移動到Pi。有如下轉移方程:

f[i][j][k]=min(f[i][j][k],f[i-1][j][k]+c(Pi-1,Pi)),階段i-1中,j和k不動,位置Pi-1的人移到Pi

f[i][Pi-1][k]=min(f[i][Pi-1][k],f[i-1][j][k]+c(j,Pi)),階段i-1中,在Pi-1位置的人不動,位置j的人移到Pi

f[i][j][Pi-1]=min(f[i][j][Pi-1],f[i-1][j][k]+c(k,Pi)),階段i-1中,在Pi-1位置的人不動,位置k的人移到Pi

注意狀態的合法性,因為不能多個人在同一個位置(見下面程式碼寫狀態轉移時的很多if)。另外原題空間給的多,不用滾動陣列,但是空間如果限制到比如128mb就需要了,但是我滾到連樣例都沒過去......所以先給出一個不用滾動陣列的程式碼

#include<bits/stdc++.h>
using namespace std;

int c[210][210],p[1010],f[1010][210][210];
int t,n,l,i,j,k;

int main(){
	scanf("%d",&t);
	while (t--){
	  scanf("%d%d",&l,&n);
	  for (i=1;i<=l;i++)
	    for (j=1;j<=l;j++) scanf("%d",&c[i][j]);
	  for (i=1;i<=n;i++) scanf("%d",&p[i]);
	  memset(f,0x3f,sizeof(f)); 
	  f[0][1][2]=0; p[0]=3;
	  for (i=1;i<=n;i++)
	    for (j=1;j<=l;j++)
	      for (k=1;k<=l;k++){
	      	if (j==p[i-1]||k==p[i-1]||j==k) continue; //*
	      	if (j!=p[i]&&k!=p[i]) 
			  f[i][j][k]=min(f[i][j][k],f[i-1][j][k]+c[p[i-1]][p[i]]);
	      	if (k!=p[i]&&p[i-1]!=p[i]) 
			  f[i][p[i-1]][k]=min(f[i][p[i-1]][k],f[i-1][j][k]+c[j][p[i]]);
			if (j!=p[i]&&p[i-1]!=p[i]) 
			  f[i][j][p[i-1]]=min(f[i][j][p[i-1]],f[i-1][j][k]+c[k][p[i]]);
		  }
	  int ans=1e9;
	  for (i=1;i<=l;i++)
	    for (j=1;j<=l;j++) ans=min(ans,f[n][i][j]);
	  printf("%d\n",ans);
	}
	return 0;
}