您的位置:首页 > 其它

【NOI2001】炮兵阵地

2016-01-24 20:12 260 查看
Description

  司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示),如下图。在每一格平原地形上最多可以布置一支炮兵部队(山地上不能够部署炮兵部队);一支炮兵部队在地图上的攻击范围如图中黑色区域所示:     如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。   

  


Input

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

  N≤100;M≤10。

Output

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

分析

在考场上,我没有走出思维的限定,一直在思考如何确定这行的状态,但是在这中途里

我却一直想若是贸然的记录这行的状态而下一行的状态就无法确定了因为这种影响是两行的。

但是我没有想到只需一直记录两行的状态就可以了。。。

这暴露了我不够谨慎,没有认真的思考,太过浮躁了。

这下就简单了,我们设f[i][j][k]为第i行的状态为j,第i-1行的状态为k,然后我们便可以推出f[i+1][l][j]。

这样的复杂度为O(2m∗2m∗2m∗n)O(2^m*2^m*2^m*n)

这样可以卡过去。。。

但是其实当我们确定了j状态和k状态还有当前的山地情况时

这样剩下的状态就变得很小了,而我们这时若再扫过去就不太明智了,

这里给出一种方法:

我们可以用or运算求出每个位置是否可以选,若为0就可以选了。

然后再暴力选一选就可以了。

当然还有几个优化,预处理每个j是否是可以的,用位运算优化。

我的代码是这样的:

[code]
    for(int i=2;i<=n-1;i++){
        w=q;
        q=w^1;
       for(int j=0;j<=de;j++)
       if (!(j&p[i])&&pd[j])
          for(int k=0;k<=de;k++)
          if (!(k&p[i-1])&&pd[k]&&!(j&k))
             for(int l=0;l<=de;l++)
             if (!(l&p[i+1])&&pd[l]&&!(l&j)&&!(l&k)){
             f[w][l][j]=max(f[w][l][j],f[q][j][k]+get[l]);
             ans=ans;
            }
        memset(f[q],0,sizeof(f[q]));
    }


Alan_cty的是这样的:

[code]    fo(i,2,n) {
        fo(j,1,tot)
            fo(k,1,tot)
                if (f[1-p][b[j]][b[k]]) {
                   int t=(mx-(b[j]|b[k]))&(mx-a[i]);
                   fo(l,1,tot)
                       if ((b[l]&t)==b[l]) f[p][b[k]][b[l]]=max(f[p][b[k]][b[l]],f[1-p][b[j]][b[k]]+c[l]);          
                }   
        p=1-p;memset(f[p],0,sizeof(f[p]));
    }


b是预处理出来的合法的数的集合。

mx = 1<< n

而这时他很聪明他没有判断j,k是否合法,而是判断f的值是否合法,这间接的判断了,而且又不浪费时间。

他比较会用位运算,用的都是比较高级的,而且他想到把许多的位运算结合在一起

那么他在l的循环里面就只用一次位运算了。

我还不会太会用减法的位运算,这让我又开了眼界。

这里的减是为了让0为可以选变成1可以选。

这样后面就可以一次性计算了。

若是and或or的时候是要求两数的1和0都要代表相同时,即:

当0是可以选时b[l]里面是1是选(变量以他的程序里面为准)而t里面若不减的话,就是0是可以选的。

那么这样1和0是不对应的,就需要转化成一样的情况,即全是1可以选,全是0可以选,

这时用减法来转换

或者我们可以用not 即这里用!(j&l)即若两个同时有1时就是不合法的。

这只是第一步,这里为什么用(b[l]&t)==b[l]?

因为我们要保证b[l]里面的所有的1都是可以用的同时t里面的对应的1也要有,所以用了and后再判断。

等于原数这种用法一般都是用在一堆位置是否合法,而单个and 1<< x是判断单个位的。

这题确实挺好的,灵活运用位运算。

我的总代吗

[code]#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
char c;
const int N=10;
int n,m,f[2][1<<N][1<<N],g[2][1<<N][1<<N],
a[N*10+5][N+5],p[N*10+5],get[1<<N],ans;
bool pd[1<<N];
int got(int x){
    int ans=0;
    while (x){
        if (x&1) ans++;
        x>>=1;
    }
    return ans;
}
int main(){
    scanf("%d %d\n",&n,&m);
    for(int i=1;i<=n;i++){
       for(int j=1;j<=m;j++){
            scanf("%c",&c);
            if (c=='H') p[i]+=1<<(j-1);
       }
       scanf("\n");
    }
    memset(f,0,sizeof(f));
    int de=(1<<m)-1;
    for(int j=0;j<=de;j++)
    if (!(j&(j>>1))&&!(j&(j>>2))&&!(j&(j<<1))&&!(j&(j<<2))) pd[j]=1;
    for(int i=0;i<=de;i++) get[i]=got(i);
    for(int j=0;j<=de;j++)
       for(int k=0;k<=de;k++)
       if (pd[j]&&pd[k]&&!(j&p[2])&&!(k&p[1]))
       f[0][j][k]=get[j]+get[k];
    int w,q;w=0;q=1;
    for(int i=2;i<=n-1;i++){
        w=q;
        q=w^1;
       for(int j=0;j<=de;j++)
       if (!(j&p[i])&&pd[j])
          for(int k=0;k<=de;k++)
          if (!(k&p[i-1])&&pd[k]&&!(j&k))
             for(int l=0;l<=de;l++)
             if (!(l&p[i+1])&&pd[l]&&!(l&j)&&!(l&k)){
             f[w][l][j]=max(f[w][l][j],f[q][j][k]+get[l]);
             ans=ans;
            }
        memset(f[q],0,sizeof(f[q]));
    }
    for(int j=0;j<=de;j++)
       for(int k=0;k<=de;k++)
          ans=max(ans,f[w][j][k]);
    printf("%d",ans);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: