3643. 【GDOI2014】拯救莫莉斯

题目内容

莫莉斯·乔是圣域里一个叱咤风云的人物,他凭借着自身超强的经济头脑,牢牢控制了圣域的石油市场。
圣域的地图可以看成是一个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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器学习模型机器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值