Time Limit: 2000MS | Memory Limit: 65536K | |
Total Submissions: 22725 | Accepted: 8800 |
Description
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。
现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
接下来的N行,每一行含有连续的M个字符('P'或者'H'),中间没有空格。按顺序表示地图中每一行的数据。N <= 100;M <= 10。
Output
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.位运算