zoj 3847 Collect Chars(ac自动机 + spfa)

题意:给一个地图, 地图上有一些字符, 每走到一个地方可以捡取若干个该字符,并且如果有字符的话最少要捡一个, 捡起来的字符放在一个队列里面,然后给一个字符串集合, 问最少要走多少步才能使队列里面最少存在一个给定的字符串。

搞法:

由于不是所有拾取的字符串达到要求,而是有一截满足条件就行, 那么就可以先把字符串集合构成ac自动机, 然后在自动机上跳转状态。

dp[i][j][k] 表示在ac自动机的第i个节点, 地图上的j行k列的最少花费,由于在一个有字符的地方可以有两个选择, 就是跳到下一个位置或者再原地停留, 继续拾取当前的字符, 所以转移就有0和1两种代价, 所以写成最短路形式的dp。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <string>
#include <cmath>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <cstdlib>
using namespace std;

#define LL long long
#define inf 0x3f3f3f3f
#define eps 1e-8
#define ULL unsigned long long
#define mnx 420
#define mxe 10020
#define mxnode 20020
#define mod 1000000007


char g[22][22];
int n, m, cnt;
char s[mnx];

int dp[20020][22][22];
int sx, sy;
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
bool in(int x, int y) {
	return x >= 1 && y >= 1 && x <= n && y <= m;
}
bool inq[20020][22][22];
struct trie {
    int ch[mxnode][26];
    bool mark[mxnode];
    int f[mxnode], sz;
    int creat() {
        memset(ch[sz], -1, sizeof(ch[sz]));
        mark[sz] = 0;
        return sz++;
    }
    void init() {
        sz = 0, creat();
    }
    void insert(char *s) {
        int t = 0;
        for(int i = 0; s[i]; ++i) {
            int c = s[i] - 'A';
            if(!~ch[t][c]) ch[t][c] = creat();
            t = ch[t][c];
        }
        mark[t] = 1;
    }
    void build() {
        queue<int> q; f[0] = 0;
        for(int i = 0; i < 26; ++i) {
            int &v = ch[0][i];
            if(!~v) v = 0;
            else f[v] = 0, q.push(v);
        }
        while(!q.empty()) {
            int t = q.front(); q.pop();
            if(mark[f[t]]) mark[t] = 1; //注意要不要加这行
            for(int i = 0; i < 26; ++i) {
                int &v = ch[t][i];
                if(!~v) v = ch[f[t]][i];
                else {
                    f[v] = ch[f[t]][i];
                    q.push(v);
                }
            }
        }
    }
    int spfa() {
		int ret = inf;
		memset(inq, 0, sizeof inq);
		memset(dp, 0x3f, sizeof dp);
		dp[0][sx][sy] = 0;
		queue<int> q;
		q.push(0), q.push(sx), q.push(sy);
		while(!q.empty()) {
			int s = q.front(); q.pop();
			int x = q.front(); q.pop();
			int y = q.front(); q.pop();
			inq[s][x][y] = 0;
			for(int i = 0; i < 4; ++i) {
				int dx = x + dir[i][0];
				int dy = y + dir[i][1];
				if(!in(dx, dy)) continue;
				if(g[dx][dy] == '#') continue;
				if(g[dx][dy] == '.') {
					if(dp[s][dx][dy] <= dp[s][x][y] + 1) continue;
					dp[s][dx][dy] = dp[s][x][y] + 1;
					if(!inq[s][dx][dy])
						inq[s][dx][dy] = 1, q.push(s), q.push(dx), q.push(dy);
					continue;
				}
				int t = ch[s][g[dx][dy]-'A'];
				if(dp[t][dx][dy] <= dp[s][x][y] + 1) continue;
				dp[t][dx][dy] = dp[s][x][y] + 1;
				if(!inq[t][dx][dy])
					inq[t][dx][dy] = 1, q.push(t), q.push(dx), q.push(dy);
			}
			if(g[x][y] != '.') {
				int t = ch[s][g[x][y]-'A'];
				if(dp[t][x][y] > dp[s][x][y]) {
					dp[t][x][y] = dp[s][x][y];
					if(!inq[t][x][y])
						inq[t][x][y] = 1, q.push(t), q.push(x), q.push(y);
				}
			}
		}
		for(int i = 1; i < sz; ++i) {
			if(mark[i] == 0) continue;
			for(int j = 1; j <= n; ++j)
				for(int k = 1; k <= m; ++k)
					ret = min(ret, dp[i][j][k]);
		}
		if(ret == inf) ret = -1;
		return ret;
    }
}ac;

int main() {
	int cas;
	scanf("%d", &cas);
	while(cas--) {
		scanf("%d%d", &n, &m);
		for(int i = 1; i <= n; ++i)
			scanf("%s", g[i] + 1);
		for(int i = 1; i <= n; ++i)
			for(int j = 1; j <= m; ++j)
				if(g[i][j] == '@') {
					sx = i, sy = j, g[i][j] = '.';
					break;
				}
		ac.init();
		int q;
		scanf("%d", &q);
		while(q--) {
			scanf("%s", s);
			ac.insert(s);
		}
		ac.build();
		printf("%d\n", ac.spfa());
	}
	return 0;
}


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值