【BZOJ1294】【SCOI2009】围豆豆 Bean(射线法+状压DP+spfa)

题目描述:围豆豆传送门

算法:

射线法+状压DP+spfa(有搜索的感觉)

做法:

射线法

首先,什么射线法?射线法是解决判断一个点是否在多边形内部的一个算法,可以点击此链接学习,它的主要思想是从这个点向右做一条射线,数数这条射线与四边形的交点有几个。如果有奇数个交点,那么这个点在图形内部;若有偶数个交点,那么它在多边形外部。
针对这道题,多边形的边只是横着或竖着,那么我们做的这条射线如果与多边形的边重合了怎么办呢?首先,我们发现交点数只与竖线有关,我们把每一段竖线想象成一条上闭下开的线段,即一个点的射线与一条竖线的上端相交时,我们记录交点数,但当它与竖线的下端相交时,我们不记录交点数。
这样,我们就可以判断那些点在多边形内部了。

状压DP

最多12个点,很符合状压DP的标准嘛(虽然我还是没想到)。设 f[i][j][s] 表示当前已走到 ( i , j ) 这个点,已经围住了状态为 s 的豆豆(当然只是暂时围住,射线与多边形有奇数个交点,但多边形可能还没有首尾相接)时的最大得分。实际上状态的完全形态是 f[x][y][i][j][s],其中 x,y 表示从 ( x , y ) 这个点开始画多边形,但由于 x,y 不同的状态不能相互更新,所以省略了。
那怎么更新呢?首先当然要枚举起点 ( x , y )了,然后呢?

SPFA

我们借用 spfa 的思想来更新它。每一个 f[i][j][s] 就好像一个个点,而更新他们就像是在求最长路,起点是 f[x][y][0],终点是 f[x][y][111111111111] 。首先由一个状态 f[i][j][s] ,从 ( i , j ) 可以向上下左右四个方向移动,每一次移动,都判断一下它暂时围住了那些豆豆,得到 f[i’][j’][s’],那么 :
f[i][j][s]=max(f[i][j][s],f[i][j][s]+)
与 spfa 的不同就是不用记录入队次数(因为每走一步,得分就会 -1,不会陷入正环而无法自拔)。
其实射♂线朝那边和一条线段那边开那边闭都无所谓,只要每次的方向一致就行。
每次写搜索题,都因为一点细节耽误半天,555
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int N=11, qsize=1000010;
int gx[] = {1,-1,0,0};
int gy[] = {0,0,1,-1};
int n, m, d, dfn, ans;
int ma[N][N], xx[N], yy[N], w[N];
int f[N][N][1<<N], v[N][N][1<<N];
char s[N<<1];
bool inq[N][N][1<<N];

struct Statu{
    int x, y, z;
}q[qsize];

int main(){
    scanf("%d%d%d",&n,&m,&d);
    for(int i=0; i<d; ++i) scanf("%d",&w[i]);
    for(int i=1, j; i<=n; ++i){
        scanf("%s",s+1);
        // 记录地图,ma[i][j]=0 表示可以走 (i,j),为 -1 表示不能走 
        for(j=1; j<=m; ++j){
            if(s[j]=='0') ma[i][j]=0;
            else{
                if(s[j]!='#'){ xx[s[j]-'1']=i; yy[s[j]-'1']=j; }
                ma[i][j]=-1;
            }
        }
    }
    int l, r, x, y, delta;
    Statu cur, next, tt;
    for(int i=1, j, k, b; i<=n; ++i){
        for(j=1; j<=m; ++j) if(~ma[i][j]){     // 枚举起点 (i,j) 
            v[i][j][0] = ++dfn; f[i][j][0]=0;   // v 的作用:重复利用 f[i][j][s] 数组 
            l = r = 0;
            q[r++] = (Statu){i,j,0}; inq[i][j][0]=true;
            while(l!=r){
                cur = q[l];
                if(cur.x==i && cur.y==j) ans = max(ans, f[cur.x][cur.y][cur.z]);
                for(b=0; b<4; ++b){
                    x=cur.x+gx[b]; y=cur.y+gy[b];
                    if(x<1 || y<1 |0| x>n || y>m || ma[x][y]) continue;
                    delta = 0;
                    if(x==cur.x){                       // 更新围住豆豆的状态(往哪射无所谓) 
                        tt = cur; if(y<cur.y) tt.y=y;   // 哪边开哪边闭无所谓  
                        for(k=0; k<d; ++k) if(yy[k]==tt.y && xx[k]<tt.x){
                            tt.z ^= 1<<k;
                            if(tt.z&(1<<k)) delta+=w[k]; else delta-=w[k];
                        }
                        next = (Statu){x,y,tt.z};
                    }else next=(Statu){x,y,cur.z};
                    if(v[x][y][next.z]!=dfn || f[x][y][next.z]<f[cur.x][cur.y][cur.z]+delta-1){     // spfa 
                        f[x][y][next.z] = f[cur.x][cur.y][cur.z]+delta-1;
                        v[x][y][next.z] = dfn;
                        if(!inq[x][y][next.z]){
                            inq[x][y][next.z] = true;
                            q[r] = next;
                            r = r+1>=qsize ? r+1-qsize : r+1;   // 循环队列  
                        }
                    }
                }
                inq[cur.x][cur.y][cur.z] = false;
                l = l+1>=qsize ? l+1-qsize : l+1;
            }
        }
    }
    printf("%d\n",ans);
    while(1);   // 调试时自带  
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值