首页 > 代码库 > 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 }
GDOI2014 拯救莫莉斯