• 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.位运算

  • 相关阅读:
    移动端布局规范-固定页头页尾-中间随高度滑动
    wangEditor编辑器 Vue基本配置项
    JavaScript命名规范基础及系统注意事项
    vue评论显示隐藏,JavaScript显示关闭
    js超链接锚点定位
    jquery的输入框自动补全功能+ajax
    第一个shell脚本
    Linux中执行shell脚本方法
    Hadoop+Hive环境部署
    Linux下安装mysql5.7
  • 原文地址:https://www.cnblogs.com/CXCXCXC/p/5079723.html
Copyright © 2020-2023  润新知