[GDKOI2016]地图[题解]

地图

题意

给定一个 \(n\times m\) 的矩阵,由字符 'S','E','X','.','?','#' 组成,其中 '#' 表示障碍物,'.' 表示空地,'?' 可以随便定义成其他五种字符中的一种。

一个合法的矩阵满足一下条件:

  • 在四连通的前提下,'S','E','X' 彼此可达。

  • 'S','E','X' 三种字符存在且仅存在一个。

求有多少个矩阵合法,答案取模 \(1e9+7\)

\(1\leq n\leq 7\)

分析

学习过插头 \(dp\) 相关知识后不难看出这是一道插头 \(dp\) 的题目。

首先要考虑的是我们如何压缩状态,由于最多有 7 列, 且不难看出 \(dp\) 过程中我们肯定需要维护连通性,保险起见,把压缩状态开到 \(8\),分别对应不同的连通块编号。则如果将我们的状态看成一个八进制的数,每一位从左往右分别表示对应的格子属于的连通块编号。

考虑如何维护。

我们还是从左往右,从上往下的按格枚举。分别考虑左边和上边的格子,插入一个格子的情况大致如下:

  • 如果是 '#' ,将对应一列的格子状态更新为 \(0\),不属于任何连通块,直接扔进下一轮。

  • 如果是三种特殊块之一,查找当前状态是否已经存在这种特殊块,如果存在,则舍弃这种情况。否则,更新如空地,但不要忘了在目标状态上标记已选。

  • 如果是 '.',即空地,大致有三种情况:

    • 左边和上面状态都是 \(0\),直接插入。

    • 左边和上面任意一边是 \(0\),合并到不是 \(0\) 的位置上去,即用和那个位置所属连通块编号的编号插入当前位置。

    • 左边和上面状态全部都存在,钦定一种情况更新,这里我选的是合并到编号小的块上去。

状态记录用 \(hash\) 表,最后的答案是所有完全更新后且三个特殊块在一个连通块内的状态。

具体的,你需要记录每种特殊块分别属于哪个连通块,这并不困难。

#include <bits/stdc++.h> 
#define int long long
using namespace std;
const char sp[5] = {'S', 'X', 'E', '.', '#'};
const int N = 14, H = 3e4 + 7, S = 1e5 + 10, mod = 1e9 + 7;

inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w;
}
int n, m, ans, opt;
int x[3], y[3], ch[N], code[N];
/*
	全局变量中的 code 表示当前状态中每一列的块属于连通块的编号
	末尾的三个位置用于记录三种特殊块所在连通块的编号情况 
*/ 
char mp[N][N];
struct node{
	//hash表 
	int siz;
	int f[S], bl[S], nex[S], first[H];
	inline void clear() { siz = 0, memset(first, -1, sizeof(first)); }
	inline void push(int x, int k){
	  int pos = x % H, i = first[pos];
	  for(; i != -1; i = nex[i])
	      if(bl[i] == x) { f[i] = (f[i] + k) % mod; return ; }
	  bl[siz] = x, f[siz] = k;
	  nex[siz] = first[pos], first[pos] = siz++;
	}
}vec[2];
//判断在当前状态中,是否回和选定的块发生冲突 
inline bool check(int i, int j)
{
   if(mp[i][j] == 'S' && code[m + 1]) return true;
   else if(mp[i][j] == 'X' && code[m + 2]) return true;
   else if(mp[i][j] == 'E' && code[m + 3]) return true;
   else return false;
}
//取出状态中储存的 code 信息 
inline void Get_code(int x) { for(register int i = 1; i <= m + 3; i++) code[i] = x & 7, x >>= 3; }
//计算当前 code 对应的状态(即将 code 压缩) 
inline int Get_val(int i, int j)
{
	if(mp[i][j] == 'S') code[m + 1] = code[j];
	else if(mp[i][j] == 'X') code[m + 2] = code[j];
	else if(mp[i][j] == 'E') code[m + 3] = code[j];
	int res = 0, cnt = 0;
	memset(ch, -1, sizeof(ch)), ch[0] = 0; // ch 记录新的编号 
	for(register int t = m + 3; t >= 1; t--){ 
	  if(ch[code[t]] == -1) ch[code[t]] = ++cnt; //重新给不同的连通块编号 
	  res <<= 3, res |= ch[code[t]];
	}
	return res;
}
inline void update(int i, int j, int opt) //更新 
{
	for(register int k = 0; k < vec[opt].siz; k++){ //遍历当前轮次的状态 
		Get_code(vec[opt].bl[k]);
		if(check(i, j)) continue ;
		int p = code[j - 1], q = code[j], id = 13; //取出左和上的状态 
		if(p) id = min(id, p);
		if(q) id = min(id, q);
		//直接进行更新 
		if(p) for(register int t = 1; t <= m + 3; t++) if (code[t] == p) code[t] = id;
		if(q) for(register int t = 1; t <= m + 3; t++) if (code[t] == q) code[t] = id;
		code[j] = id, vec[opt ^ 1].push(Get_val(i, j), vec[opt].f[k]); //扔进下一轮 
	}
}
inline void change(int i, int j, int opt) //专门更新 '#',其他状态都不变,但当钱列的块的状态需要改变为 0  
{
	for(register int k = 0; k < vec[opt].siz; k++)
		Get_code(vec[opt].bl[k]), code[j] = 0, vec[opt ^ 1].push(Get_val(i, j), vec[opt].f[k]); //扔进下一轮 
}
inline void Sol()
{
	vec[0].clear(), vec[1].clear(), vec[0].push(0, 1); //初始化邻接表 
	for(register int i = 1; i <= n; i++){
		for(register int j = 1; j <= m; j++){
	   	if(mp[i][j] != '?'){
	         if(mp[i][j] == '#') change(i, j, opt); //如果是 '#' 
	         else update(i, j, opt); //正常更新 
	      }
	      else{
	         for(register int t = 0; t < 5; t++){ //枚举可以变成的 5 中块 
	            if(t < 3 && x[t] != -1) continue; //如果特殊块在给定的块中已经存在,则不考虑这种情况 
	            mp[i][j] = sp[t];
	            if(mp[i][j] == '#') change(i, j, opt); 
	            else update(i, j, opt);
	         }
	      }
	      vec[opt].clear(), opt ^= 1;
		}
	}
}
signed main()
{
	n = read(), m = read();
	memset(x, -1, sizeof(x)), memset(y, -1, sizeof(y));
	for(register int i = 1; i <= n; i++){
		scanf("%s", mp[i] + 1);
		//记录一下已经给定的位置中三种特殊块的存在情况 
		for(register int j = 1; j <= m; j++){
			if(mp[i][j] == 'S') x[0] = i, y[0] = j;
			else if(mp[i][j] == 'X') x[1] = i, y[1] = j;
			else if(mp[i][j] == 'E') x[2] = i, y[2] = j;
		}
	}
	Sol();
	for(register int i = 0; i < vec[opt].siz - 1; i++){
		Get_code(vec[opt].bl[i]);
		if((!code[m + 1]) || (!code[m + 2]) || (!code[m + 3])) continue;
		int t = code[m + 1];
		if(code[m + 2] != t || code[m + 3] != t) continue;
		ans = (ans + vec[opt].f[i]) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2022-05-06 21:15  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(27)  评论(0编辑  收藏  举报