BZOJ 1294: [SCOI2009]围豆豆Bean 状压DP,SPFA,计算集合射线法

这里写图片描述

Input
第一行两个整数N和M,为矩阵的边长。 第二行一个整数D,为豆子的总个数。 第三行包含D个整数V1到VD,分别为每颗豆子的分值。 接着N行有一个N×M的字符矩阵来描述游戏矩阵状态,0表示空格,#表示障碍物。而数字1到9分别表示对应编号的豆子。
Output
仅包含一个整数,为最高可能获得的分值。
Sample Input
3 8

3

30 -100 30

00000000

010203#0

00000000

Sample Output
38

HINT

50%的数据满足1≤D≤3。
100%的数据满足1≤D≤9,1≤N, M≤10,-10000≤Vi≤10000。

解法:看到那个图我还以为是基于连通性的DP来着,又看了一眼数据范围感觉应该是状压,但是在那个路径围成的多边形里面如何判断呢?我学到了一种神奇的射线法,做法是这样的,从一点向随机方向引一条射线,如果射线和多边形的边相交奇数次,说明点在多边形内。那么我们真的需要用计算几何的知识来做射线判断?不需要,我们实际上这样写就可以了。

int segcross(int x, int y, int nx, int ny, int S)
{
    for(int i=1; i<=D; i++){
        if(((x<b[i].x && nx>=b[i].x) || (x>=b[i].x && nx<b[i].x)) && y > b[i].y){
            S ^= 1<<(i-1);
        }
    }
    return S;
}

为毛?我们射线是和方向有关的,如果我们坐标恰好也只限定一个方向刚好就满足了,可以在纸上画一下,接下来就是裸的SPFA了。

复杂度:

O(n*m*(2^m+O(spfa)))

//BZOJ 1294

#include <bits/stdc++.h>
using namespace std;

int dir[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
const int maxn = 100010;
const int inf = 0x3f3f3f3f;
int n, m, D;

struct node1{
    int x, y;
    node1(){}
    node1(int x, int y) : x(x), y(y) {}
}b[maxn];

struct node2{
    int i, j, s;
    node2(){}
    node2(int i, int j, int s) : i(i), j(j), s(s) {}
};

int segcross(int x, int y, int nx, int ny, int S)
{
    for(int i=1; i<=D; i++){
        if(((x<b[i].x && nx>=b[i].x) || (x>=b[i].x && nx<b[i].x)) && y > b[i].y){
            S ^= 1<<(i-1);
        }
    }
    return S;
}

char mp[20][20];
int w[maxn], ans, dp[12][12][1010];
bool inq[12][12][1010];

void spfa(int sx, int sy){
    queue <node2> que;
    que.push(node2{sx, sy, 0});
    memset(dp, 0x3f, sizeof(dp));
    dp[sx][sy][0]=0;
    inq[sx][sy][0]=1;
    while(!que.empty()){
        node2 u = que.front(); que.pop();
        inq[u.i][u.j][u.s] = 0;
        for(int k=0; k<4; k++){
            int tx = u.i + dir[k][0], ty = u.j + dir[k][1];
            if(tx > 0 && tx <= n && ty > 0 && ty <= m){
                if(mp[tx][ty] != '0') continue;
                int s = segcross(u.i, u.j, tx, ty, u.s);
                if(dp[tx][ty][s] > dp[u.i][u.j][u.s]+1){
                    dp[tx][ty][s] = dp[u.i][u.j][u.s]+1;
                    if(!inq[tx][ty][s]){
                        inq[tx][ty][s]=1;
                        que.push(node2{tx,ty,s});
                    }
                }
            }
        }
    }
    int mask = 1<<D;
    for(int i=0; i<mask; i++){
        int res = -dp[sx][sy][i];
        for(int j = 1; j <= D; j++){
            if(i&(1<<(j-1))) res += w[j];
        }
        ans = max(ans, res);
    }
}

int main()
{
    scanf("%d%d%d", &n, &m, &D);
    for(int i=1; i<=D; i++) scanf("%d", &w[i]);
    for(int i=1; i<=n; i++){
        scanf("%s", mp[i]+1);
        for(int j=1; j<=m; j++){
            if(mp[i][j]>'0'&&mp[i][j]<='9'){
                int x  = mp[i][j]-'0';
                b[x] = node1(i, j);
            }
        }
    }
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            if(mp[i][j]=='0'){
                spfa(i, j);
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值