← 全部文章

CF613E Puzzle Lover 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

CF613E Puzzle Lover

题意

  • 给定一个 2×n2 \times n 的矩阵,每个位置上有一个小写字母。
  • 有一个长度为 kk 的小写字符串 ww,询问矩阵中有多少条有向路径满足以下条件:
    • 路径上的字母连起来恰好为 ww。
    • 不多次经过同一个位置。
    • 只能向上下左右四个方向走。
  • n,k≤2×103n,k \le 2 \times 10^3,答案对 109+710^9+7 取模。

题解

假设 n,kn,k 同阶。

考虑头尾不在同一列的情况,不妨设头在尾的左侧。对于头在尾的右侧的情况,可以将 ww 翻转之后在求一遍。

当头在尾的左侧时,我们将路径划分成连续的三个部分:

  1. 头所在的列及其左侧的部分;
  2. 在头的右侧尾的左侧的部分;
  3. 尾所在的列及其右侧的部分。

对于任意一条路径,它的第 1/31/3 部分都肯定非空,而第 22 部分可能为空。

下面我们依次考虑每一个部分:

  1. 这部分要么是一个格子,要么是一个 U 字形,我们可以对于每个格子求出「若第 11 部分在这个格子结束,所有可能的第 11 部分的长度」,借助 Hash 即可做到 O(n2)\mathcal O(n^2)。
  2. 这部分不能往左,考虑 dp,设 fi,j,kf_{i,j,k} 表示「若第 22 部分在 (i,j)(i,j) 结束,上一个格子为 (i,j−1)(i,j-1),且第 1,21,2 部分的长度和为 kk 的方案数」,设 gi,j,kg_{i,j,k} 表示「若第 22 部分在 (i,j)(i,j) 结束,上一个格子其同一列,且第 1,21,2 部分的长度和为 kk 的方案数」,从左到右转移,转移时枚举下一个格子即可,时间复杂度为 O(n2)\mathcal O(n^2)。
  3. 与第 11 部分一样,对于每个格子求出「若第 33 部分从这个格子开始,所有可能的第 33 部分的长度」,然后与第 22 部分接上,即第 22 部分的结束的格子的右边一个格子就是第 33 部分开始的格子,同样借助 Hash 可以做到 O(n2)\mathcal O(n^2)。

还剩下头尾在同一列的情况,可以发现我们直接在考虑第 1/31/3 部分时顺便解决掉就好了,注意翻转前后会重复计算一次,最后除掉就好了。

总时间复杂度 O(n2)\mathcal O(n^2)。

代码

#define Hash pair<modint, modint>
const Hash B = mp(131, 13331);
inline Hash operator + (Hash a, Hash b) { return mp(a.fi + b.fi, a.se + b.se); }
inline Hash operator - (Hash a, Hash b) { return mp(a.fi - b.fi, a.se - b.se); }
inline Hash operator * (Hash a, Hash b) { return mp(a.fi * b.fi, a.se * b.se); }
inline Hash operator + (Hash a, int b) { return mp(a.fi + b, a.se + b); }

const int N = 2e3 + 7;
int n, m;
char s[3][N];
modint f[2][N][N], g[2][N][N], ans, now;
Hash p[N], h[2][3][N];
bool a[2][N][N], b[2][N][N];

inline Hash H(int o, int i, int l, int r) {
	if (!o) return h[o][i][r] - h[o][i][l-1] * p[r-l+1];
	return h[o][i][l] - h[o][i][r+1] * p[r-l+1];
}

inline bool pd(int o, int i, int j, int k) {
	if (!o) {
		if (j < k) return 0;
		if (H(1, i ^ 1, j - k + 1, j) != H(0, 2, 1, k)) return 0;
		if (H(0, i, j - k + 1, j) != H(0, 2, k + 1, 2 * k)) return 0;
		return 1;
	}
	if (j + k - 1 > n) return 0;
	if (H(0, i, j, j + k - 1) != H(0, 2, m - 2 * k + 1, m - k)) return 0;
	if (H(1, i ^ 1, j, j + k - 1) != H(0, 2, m - k + 1, m)) return 0;
	return 1; 
}

inline void solve() {
	for (int i = 1; i <= m; i++) h[0][2][i] = h[0][2][i-1] * B + s[2][i];
	for (int i = 0; i <= 1; i++)
		for (int j = 1; j <= n; j++)
			for (int k = 1; k <= m; k++) {
				f[i][j][k] = g[i][j][k] = 0;
				if (k == 1) {
					g[i][j][k] = a[i][j][k] = s[i][j] == s[2][1];
					b[i][j][k] = s[i][j] == s[2][m];
				} else if (!(k & 1)) {
					g[i][j][k] = a[i][j][k] = pd(0, i, j, k / 2);
					b[i][j][k] = pd(1, i, j, k / 2);
				}
				if (k == m) {
					now += a[i][j][k];
					if (m > 2) now += b[i][j][k];
				}
			}
	dbg(ans.x);
	for (int j = 1; j <= n; j++)
		for (int k = 1; k <= m; k++) {
			for (int i = 0; i <= 1; i++)
				if (s[i][j] == s[2][k])
					f[i][j][k] += f[i][j-1][k-1] + g[i][j-1][k-1];
			for (int i = 0; i <= 1; i++)
				if (s[i][j] == s[2][k])
					g[i][j][k] += f[i^1][j][k-1];
			for (int i = 0; i <= 1; i++)
				if (b[i][j+1][m-k])
					ans += f[i][j][k] + g[i][j][k];
		}
}

int main() {
	rds(s[0], n), rds(s[1], n), rds(s[2], m), p[0] = mp(1, 1);
	for (int i = 1; i <= max(n, m); i++) p[i] = p[i-1] * B;
	for (int i = 0; i <= 1; i++) {
		for (int j = 1; j <= n; j++)
			h[0][i][j] = h[0][i][j-1] * B + s[i][j];
		for (int j = n; j; j--)
			h[1][i][j] = h[1][i][j+1] * B + s[i][j];
	}
	solve(), reverse(s[2] + 1, s[2] + m + 1), solve(), print(ans + now / 2);
	return 0;
}

标签:DP、Hash

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…