Description
一个公司有三个移动服务员。如果某个地方有一个请求,某个员工必须赶到那个地方去(那个地方没有其他员工),某一时刻只有一个员工能移动。被请求后,他才能移动,不允许在同样的位置出现两个员工。从p到q移动一个员工,需要花费c(p,q)。这个函数没有必要对称,但是c(p,p)=0。公司必须满足所有的请求。目标是最小化公司花费。
Input
第一行有两个整数L,N(3<=L<=200, 1<=N<=1000)。L是位置数 ;N是请求数。每个位置从1到L编号。下L行每行包含L个非负整数。第i+1行的第j个数表示c(i,j) ,并且它小于2000。最后一行包含N个数,是请求列表。一开始三个服务员分别在位置1,2,3。
Output
一个数M,表示最小服务花费。
这道题第一次看打了一个暴力,想骗点分
void dfs(int x,int y,int z,int v,int num)
{
if (num==m+1)
{
ans=min(ans,v);
return ;
}
if (x==point[num]) dfs(point[num],y,z,v+c[x][point[num]],num+1);
else if (y==point[num])dfs(x,point[num],z,v+c[y][point[num]],num+1);
else if (z==point[num]) dfs(x,y,point[num],v+c[z][point[num]],num+1);
else
{
dfs(point[num],y,z,v+c[x][point[num]],num+1);
dfs(x,point[num],z,v+c[y][point[num]],num+1);
dfs(x,y,point[num],v+c[z][point[num]],num+1);
}
}
但是数据范围虽然不算太大,但是足以卡死暴力,那么仔细分析一下发现这道题动态规划是可行的,但是当时用了四维数组+滚动空间虽然没爆,但是时间O(N*L^3)显然会T
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int i,j,n,m,k,l;
int c[1003][1003],point[1003],ans;
int f[202][202][203][5];
int main()
{
freopen("service.in","r",stdin);
freopen("service.out","w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
scanf("%d",&c[i][j]);
for (i=1;i<=m;i++)
scanf("%d",&point[i]);
ans=2000000000;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
for (k=1;k<=n;k++)
f[i][j][k][1]=f[i][j][k][2]=2000000000;
f[1][2][3][1]=0;
for (i=1;i<=m;i++)
{
for (j=1;j<=n;j++)
for (k=1;k<=n;k++)
for (l=1;l<=n;l++)
if (j!=k&&k!=l&&l!=j)
{
if (j==point[i]||k==point[i]||l==point[i]) f[j][k][l][2]=min(f[j][k][l][1],f[j][k][l][2]);
else
{
f[point[i]][k][l][2]=min(f[point[i]][k][l][2],f[j][k][l][1]+c[j][point[i]]);
f[j][point[i]][l][2]=min(f[j][point[i]][l][2],f[j][k][l][1]+c[k][point[i]]);
f[j][k][point[i]][2]=min(f[j][k][point[i]][2],f[j][k][l][1]+c[l][point[i]]);
}
}
for (j=1;j<=n;j++)
for (k=1;k<=n;k++)
for (l=1;l<=n;l++)
if (j!=k&&k!=l&&l!=j)
f[j][k][l][1]=f[j][k][l][2],f[j][k][l][2]=2000000000;
}
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
for (k=1;k<=n;k++)
if (j!=k&&k!=i&&i!=j)
if (i==point[m]||j==point[m]||k==point[m])
ans=min(ans,f[i][j][k][1]);
printf("%d",ans);
}
那么动归的思路找到了,可是如何减少一种循环呢?仔细分析发现所有有用的状态中必有一维是当前请求服务的位置,那么根据这个共性就可以减少一维。只需每次记录一下上一个的位置即可
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
int i,j,n,m,k,l;
int c[1003][1003],x,cur,last,ans;
int f[5][202][202];
int main()
{
freopen("service.in","r",stdin);
freopen("service.out","w",stdout);
scanf("%d%d",&n,&m);
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
scanf("%d",&c[i][j]);
memset(f,127/3,sizeof(f));
cur=0; last=1;
f[cur][2][3]=0;
for (i=1;i<=m;i++)
{
scanf("%d",&x);
for (j=1;j<=n;j++)
for (k=1;k<=n;k++)
if (f[cur][j][k]>-1)
{
if (j!=x&&k!=x)
{
if(f[1-cur][j][k]==-1||f[1-cur][j][k]>f[cur][j][k]+c[last][x])
f[1-cur][j][k]=f[cur][j][k]+c[last][x];
}
if (x!=last&&k!=x)
{
if (f[1-cur][last][k]==-1||f[1-cur][last][k]>f[cur][j][k]+c[j][x])
f[1-cur][last][k]=f[cur][j][k]+c[j][x];
}
if (x!=last&&j!=x)
{
if (f[1-cur][j][last]==-1||f[1-cur][j][last]>f[cur][j][k]+c[k][x])
f[1-cur][j][last]=f[cur][j][k]+c[k][x];
}
f[cur][j][k]=-1;
}
cur=1-cur;
last=x;
}
ans=2000000000;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (f[cur][i][j]>-1&&f[cur][i][j]<ans)
ans=f[cur][i][j];
printf("%d",ans);
}