题目内容
莫莉斯·乔是圣域里一个叱咤风云的人物,他凭借着自身超强的经济头脑,牢牢控制了圣域的石油市场。
圣域的地图可以看成是一个n*m的矩阵。每个整数坐标点(x , y)表示一座城市(1<=x<= n, 1<=y<=m)。两座城市间相邻的定义为:对于城市(Ax, Ay)和城市(Bx, By),满足(Ax - Bx)^2 + (Ay - By)^2 = 1。
由于圣域的石油贸易总量很大,莫莉斯意识到不能让每笔石油订购单都从同一个油库里发货。为了提高效率,莫莉斯·乔决定在其中一些城市里建造油库,最终使得每一个城市X都满足下列条件之一:
1.该城市X内建有油库,
2.某城市Y内建有油库,且城市X与城市Y相邻。
与地球类似,圣域里不同城市间的地价可能也会有所不同,所以莫莉斯想让完成目标的总花费尽可能少。如果存在多组方案,为了方便管理,莫莉斯会选择建造较少的油库个数。
解题思路
这一道题用的是状压DP。
首先我们要明白一行是否合法,只会和它本身,它的上一行和它的下一行有关系。
接着我们便可以设一个f[i,j,k]表示为第i行,这一行的状态为k,上一行的状态为j。
然后,我们便可以得到状态转移方程:
f[i+1,j,k]=min(f[i,l,j]+c[i+1,k]);
c[i,j]为第i行状态为j时所需要的最小成本。
至于怎么求,稍微思考一下就知道了。
如果成本相同,那么就要选数量最小的。
这只要在多开一个数组就行了。
最关键的问题出现了:我们该如何判定一行是否合法?
举个例子:
10100
01000
00100
我们可以发现,只要上一行或下一行或左边或右边和自己有1就行了。
我们便可以发现一个公式:k or l or j*2 or j/2 or j
如果这一个公式等于2^m-1那就是合法的
不过还有一个问题,如果j*2多了一位,那该怎么办?
很简单,与一下就行了!
最后公式:(k or l or j*2 or j/2 or j) and (2^m-1)=2^m-1
代码
#include<cstdio>
#include<cstring>
using namespace std;
int c[60][150];
int f[60][150][150];
int g[60][150][150];
int a[52][52];
int b[50];
int n,m;
int mymin(int x,int y) {return x<y?x:y;}
void dfs(int d,int k)
{
if(k>m)
{
int sum1=0,sum2=0;
for(int i=1;i<=m;i++)
{
sum1=sum1*2+b[i];
if(b[i]==1)
{
sum2+=a[d][i];
}
}
c[d][sum1]=sum2;
}
else
{
b[k]=1;
dfs(d,k+1);
b[k]=0;
dfs(d,k+1);
}
}
int main()
{
scanf("%d%d",&n,&m);
if(n<m)
{
int t=n;
n=m;
m=t;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;i++)
{
dfs(i,1);
}
int e=1;
for(int i=1;i<=m;i++) e*=2;
e--;
for(int i=1;i<=n+1;i++)
{
for(int j=0;j<=e;j++)
{
for(int k=0;k<=e;k++)
{
f[i][j][k]=999999999;
g[i][j][k]=0;
}
}
}
for(int i=0;i<=e;i++)
{
f[1][0][i]=c[1][i];
int zz=i,zzz=0;
while(zz>0)
{
if(zz%2==1)
{
zzz++;
}
zz/=2;
}
g[1][0][i]=zzz;
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=e;j++)
{
for(int k=0;k<=e;k++)
{
for(int l=0;l<=e;l++)
{
int x=j/2,y=j*2;
if(((k | l | x | y | j) & (e))==e)
{
if(f[i+1][j][k]>f[i][l][j]+c[i+1][k])
{
f[i+1][j][k]=f[i][l][j]+c[i+1][k];
int z=0,ee=k;
while(ee>0)
{
if(ee%2==1) z++;
ee/=2;
}
g[i+1][j][k]=g[i][l][j]+z;
}
}
}
}
}
}
int min=999999999,min2;
for(int i=0;i<=e;i++)
{
if(f[n+1][i][0]<min)
{
min=f[n+1][i][0];
min2=g[n+1][i][0];
}
if(f[n+1][i][0]==min)
{
if(g[n+1][i][0]<min2)
{
min2=g[n+1][i][0];
}
}
}
printf("%d %d\n",min2,min);
return 0;
}