NOI[2001] 炮兵阵地

Time Limit: 2000MS   Memory Limit: 65536K
Total Submissions: 22725   Accepted: 8800

Description

司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用"H" 表示),也可能是平原(用"P"表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示: 

如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。 

Input

第一行包含两个由空格分割开的正整数,分别表示N和M; 
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。

Output

仅一行,包含一个整数K,表示最多能摆放的炮兵部队的数量。

Sample Input

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

Sample Output

6

Source

  
题解:

50分析:盲目搜索

    初学者一般看到此题估计会无从着手。如果用“万能”的搜索算法,回溯或者枚举所有的状态来求解的话,那算法复杂度将是O(2^(m*n))。
    又考虑到m<=10,n<=100,这将是个及其恐怖的工作。
    大家知道凡是指数级的算法一般不能作用于较大数据的运算。

 动态规划

    观察地图,对于任何一行的炮兵放置都与其上下几行的放置有关。如果我们逐行的放置炮兵,并且每次都知道前面每行所有放置法的最优解(即最大炮兵数),那么我们要求放置到当行时某种放置法的最优解,就可以枚举前面与其兼容(即不会发生冲突)的所有放置法,从中求得本行的最优解。
    那么就可以把N*M行的最优解装换成了(N-1)*M行的最优解。此算法的基础在于,每行的状态(炮兵放置情况)只与前几行的状态有关。
    这满足最优子问题和无后效性的性质,因此可以使用动态规划求解。
    最优子问题大家都知道。无后效性就是指最优解只与状态有关,而与到达这种状态的路径无关。
    此问题的状态就是指该行的炮兵放置法

动态方程

    f[i][j][k] = max{f[i-1][k][p]+c[j]},(枚举p的每种状态) 
    f[i][j][k]表示第i行状态为s[j],第i-1行状态为s[k]的最大炮兵数,且s[j],s[k],s[p]及地形之间互不冲突
    算法复杂度:O(N*S*S*S),N为行数,S为总状态数

问题如何描述

    好了,思路大致都准备好了。但如何描述问题呢?
    动态规划的关键就在于如何描述状态。如何用二进制串表示状态的话,那么在代码中表示起来将很复杂,不利于编写代码。
    怎么办?

状态压缩

    现在引入最关键的感念,状态压缩
    我们把一个二进制串的相应十进制数称为该二进制串的压缩码,这就将一个二进制串压缩为一个简单的十进制状态。
    伴随着这个概念而来的是其相应的位运算,&, |, !,<<, >>等。

相关运算

    我们现在就可以用与运算&判断两个压缩状态间、压缩状态与压缩地图间是否冲突。
    用移位运算>>和求余运算%计算压缩状态所包含的炮兵数

困惑?

    现在似乎大功告成了,但是所写的代码提交运行结果为,Time Limit Exceed,即超时。
    为什么呢?

复杂度解析

    看看题目条件吧!Time Limit: 2000MS    Memory Limit:65536K 
    我们采用压缩二进制方式来表示一行的所有状态,那么会有每行会有2^10即1024个状态。因此在最坏情况下(M=10,N=100,所有地点都是平原),会将扫描100*1024*1024*1024(10^11,远远超过2S),因此不可取。
    O(N*S*S*S)不可取么?

算法加速

    不!
    仔细分析,状态数S真的是2^10么?
    显然,有些是伪状态,自身就是个矛盾体。那么可以提前摒弃这些伪状态。记过计算,单独一行(10列)的合法状态数只有60个!!

  下面是一段计算状态个数的代码,我用递归实现:

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int a[2000];
 4 int cnt;
 5 int N=10;
 6 inline void print(){
 7     int tot=0;
 8     for(int i=1;i<=10;i++){
 9         cout<<a[i]<<" ";
10         if(a[i]==1) tot++;
11     }
12     cout<<"tot="<<tot<<endl;
13 }
14 inline void ser(int step){
15     if(step>2&&a[step-1]==0&&a[step-2]==0){
16         a[step]=1; if(step==N) cnt++,print();else ser(step+1); 
17         a[step]=0; if(step==N) cnt++,print();else ser(step+1);
18     }
19     else if(step>2){
20         a[step]=0; if(step==N) cnt++,print();else ser(step+1); 
21     }
22     else if(step<=2){
23         if(step==1){
24             a[step]=1; ser(step+1);
25             a[step]=0; ser(step+1);
26         }
27         if(step==2){
28             if(a[step-1]==0){
29                 a[step]=1; ser(step+1);
30                 a[step]=0; ser(step+1);
31             }
32             else{
33                 a[step]=0; ser(step+1);
34             }
35         }
36     }
37 }
38 int main(){
39     cout<<"START:"<<endl;
40     ser(1);
41     cout<<cnt<<endl;
42     return 0;
43 }

  知道了状态不超过60之后,就可以开f[101][61][61]了!!!

  AC代码如下:

 

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstdlib>
 4 #include<cmath>
 5 #include<algorithm>
 6 #include<cstring>
 7 #include<queue>
 8 #include<vector>
 9 using namespace std;
10 char map[101][11];//地图 
11 int surface[101];//地形状态压缩成的十进制数 
12 int state[61];//state[i] 表示第i种放法 对应压缩成的十进制数字 
13 int stanum[61];//stanum[i] 表示第i种放法 相应状态的炮兵数量 
14 int f[101][61][61];//动态规划存储矩阵 
15 int main(){;
16     int row,col;
17     cin>>row>>col;
18     for(int i=0;i<row;i++) cin>>map[i];
19     /*因为最大列数不大于10,故可用dp进行状态压缩 
20     注:所谓状态压缩即如:PHPP 可以用二进制0100表示,用十进制存储为4。 
21     本题因其反向对称性,为了方便压缩,故上边实例压缩成0010,用2表示,不影响求解。*/
22     for(int i=0;i<row;i++){
23         for (int j=0;j<col;j++){
24            if(map[i][j]=='H') surface[i]+=(1<<j);
25         }
26     }
27     /*同地图状态压缩,对排列阵型的状态进行压缩,并算出相应阵型的数量。 
28     //如 PHPP有0001 0010 1000 1001 摆法,相应的压缩为 1 2 6 7 相关人数为 1 1 1 2*/
29     int snum=0; 
30     for(int i=0;i<(1<<col);i++){//i表示某十进制数,列数为 col则有 0~2^col-1种状态 
31         int temp=i;
32         if( ((i<<1)&i)!=0 || ((i<<2)&i)!=0 ) continue;//判断图是否兼容
33         stanum[snum]=temp%2;
34         while(temp=(temp>>1)) stanum[snum]+=temp%2;//计算能放多少个炮兵  
35         state[snum++]=i;
36     }
37     /*动态规划状态转移方程: 
38     //f[i][j][k] = max{f[i-1][k][l]+stanum[j]}, 
39     //f[i][j][k]表示第i行状态为s[j],第i-1行状态为s[k]的最大炮兵数 
40     //枚举l的每种状态,且state[j],state[k],state[l],地形互不冲突*/
41     
42     //第一行放置所有炮兵情况 
43     for (int i=0;i<snum;i++){//枚举状态数 
44         //仅仅进行一次位与操作,即可知道是否摆放与地形冲突。以后状态判断类似
45         if((state[i]&surface[0])!=0) continue; //保证有'H'的地方无炮兵 
46         f[0][i][0]=stanum[i]; 
47     }
48     //第二行放置所有炮兵情况 
49     for(int i=0;i<snum;i++){
50         if(state[i]&surface[1]) continue; //保证有'H'的地方无炮兵 
51         for (int k=0;k<snum;k++){//枚举第1行的状态 
52             if(state[k]&surface[0]) continue;//保证第一行的状态与第一行的实际地形不冲突 
53             if(state[i]&state[k]) continue;//保证第一行的状态与第二行的状态不冲突
54             f[1][i][k]=max(f[1][i][k],f[0][k][0]+stanum[i]); 
55         }
56     }
57     //之后的炮兵放置情况 
58     for(int i=2;i<=row-1;i++){//枚举 2~row-1行 
59         for(int j=0;j<snum;j++){//枚举第 i行的状态 
60             if(surface[i]&state[j]) continue;//保证第i行的状态与实际地形不冲突 
61             for(int k=0;k<snum;k++){//枚举 i-1行的状态 
62                 if(surface[i-1]&state[k]) continue;//不解释。。。 
63                 if(state[j]&state[k]) continue; 
64                 for(int l=0;l<snum;l++){//枚举第i-2行的状态 
65                     if(state[l]&surface[i-2]) continue; 
66                     if(state[j]&state[l]||state[k]&state[l]) continue;
67                     f[i][j][k]=max(f[i][j][k],f[i-1][k][l]+stanum[j]); 
68                 }
69             }
70         }
71     }
72     //找出最优解 
73     int ANS=0;
74     for(int i=0;i<snum;i++){
75         for(int j=0;j<snum;j++){
76             ANS=max(ANS,f[row-1][i][j]);
77         }
78     }
79     printf("%d",ANS);
80     return 0;
81 }

 

                            小结

    1.最优子结构和无后效性
    2.压缩状态的动态规划
    3.位运算

posted @ 2015-12-27 11:21  CXCXCXC  阅读(454)  评论(0编辑  收藏  举报