首页 > 代码库 > GDOI2014 拯救莫莉斯

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相邻。

与地球类似,圣域里不同城市间的地价可能也会有所不同,所以莫莉斯想让完成目标的总花费尽可能少。如果存在多组方案,为了方便管理,莫莉斯会选择建造较少的油库个数。

输入格式

第一行两个正整数n,m ( n * m <= 50 且m<=n),表示矩阵的大小。

接下来一个n行m列的矩阵F,Fi, j表示在城市(i,j)建造油库的代价。

输出格式

输出两个数,建造方案的油库个数和方案的总代价。

 

输入样例:

输出样例:

3 3

6 5 4

1 2 3

7 8 9

3 6

数据范围

对于30%数据满足 n * m <= 25;

对于100%数据满足n * m <= 50; 0 <= Fi, j <= 100000

  这道题再次教给我们一个道理,暴力出奇迹。

  当时根据“经验”——考试必有爆搜,果断当搜索题干(其实也是因为看成了n≤50,m≤50,没想到状压),按照之前“翻格子”的思路,挨行搜索,每到一行行末检测上一行是否全部被覆盖,否直接return ,知道最后一行,中间通过若当前解小于当前最优解,直接返回,先搜油库少的情况后搜油库多的情况,等等等等一系列通用剪枝,然后用rand()出来的数据一测,大约在n*m=35左右就会T掉。其实,我的暴力算法和测试数据本身有着极大的关系,也就是说数据从某种意义上讲决定了我的算法复杂度,比如从我自己造的数据来看2*25绝对没有十几分钟出不来,但最后还真过了,所以最后只有1*50的数据T成狗,(不是很理解官方题解说的30~60分)其他貌似速度还是挺不错的。 


 

  淡扯完了,现在是正解。

  由m<=n可知,m最大才为7,因此果断状压,让我们先预估一下复杂度大概是多少,2^7是128,因此当m=n=7时该数最大,那么最大时所允许的复杂度多少呢?n*2^(3*m)中的n都可以当一个较大的常数算了,又因为本题中对每一行是否被完全覆盖与他自己和上下行都有关,正好是3行,因此,凭借职业敏感和推断状压应当是上中下3行,当然注意边界。那么我们就先顺着这个思路向下走,如果开三维状压的话是2097152,完全够用,但真的用开三维吗,显然不用,因为下一行的状态由本行转移,在转移的时候判断一下能否转移一下即可,那么数组大小就变为了16324,貌似小了一点啊,但别忘了,还有行,因此就为816200,这就差不多了,既然状压两维,行一维,那么它表示的意义也就清楚了f[i][j][k],就表示第i行,上一行(买油库)的状态为j,本行买油库的状态为k,且前i-1行全部被覆盖的解的最优值,至于要开几座油库,再存一个相同的g数组表示即可。

  转移方程是比较有意思的,不难想,但要注意优先级:

  技术分享

  至于最终答案嘛,就是f[n+1][x][0]中的最小值,x任意。最后直接输出它和与它对应的g数组即可,注意一下顺序。

技术分享
 1 #include<iostream>
 2 #include<cstdlib>
 3 #include<cstdio>
 4 #include<cstring>
 5 #include<algorithm>
 6 #include<cmath>
 7 #include<map>
 8 using namespace std;
 9 int n,m,a[55][55];
10 int f[55][1<<8][1<<8];
11 int g[55][1<<8][1<<8];
12 int main(){
13     scanf("%d%d",&n,&m);
14     for(int i=1;i<=n;i++)
15     {
16         for(int j=1;j<=m;j++)
17         {
18             scanf("%d",&a[i][j]);
19         }
20     }
21     memset(f,0x7f,sizeof(f));
22     memset(g,0x3f,sizeof(g));
23     for(int i=0;i<(1<<m);i++)
24     {
25         int now=0;
26         f[1][0][i]=g[1][0][i]=0;
27         for(int k=1;;k++)
28         {
29             if((1<<(k-1))&i)
30             {
31                 now|=(1<<(k-1));
32                 f[1][0][i]+=a[1][k];
33                 g[1][0][i]++;
34             }
35             if(now>=i)
36                 break;
37         }
38     }
39     for(int i=1;i<=n;i++)
40     {
41         for(int j=0;j<(1<<m);j++)
42         {
43             for(int k=0;k<(1<<m);k++)
44             {
45                 for(int l=0;l<(1<<m);l++)
46                 {
47                     int now=0,sum=0,js=0;
48                     for(int t=1;;t++)
49                     {
50                         if((1<<(t-1))&l)
51                         {
52                             js++;
53                             now|=(1<<(t-1));
54                             sum+=a[i+1][t];
55                         }
56                         if(now>=l)
57                             break;
58                     }
59                     if(((l|j|k|(k>>1)|(k<<1))&((1<<m)-1))==((1<<m)-1))
60                     {
61                         if((f[i+1][k][l]>f[i][j][k]+sum)||(f[i][j][k]+sum==f[i+1][k][l]&&g[i+1][k][l]>g[i][j][k]+js))
62                         {
63                             f[i+1][k][l]=f[i][j][k]+sum;
64                             g[i+1][k][l]=g[i][j][k]+js;
65                         }
66                     }
67                 }
68             }
69         }
70     }
71     int ans=0x7fffffff,sum=0x7fffffff;
72     for(int i=0;i<(1<<m);i++)
73     {
74         if(ans>f[n+1][i][0]||(ans==f[n+1][i][0]&&sum>g[n+1][i][0]))
75         {
76             ans=f[n+1][i][0];
77             sum=g[n+1][i][0];
78         }
79     }
80     printf("%d %d\n",sum,ans);
81     //while(1);
82     return 0;
83 }
View Code

 

GDOI2014 拯救莫莉斯