• 题目传送门

  • 看到 \(m\le 12\) 和 \(c\le 6\) ,容易想到状压 DP

  • 考虑转化成 \(3^{nm}\) 减去不合法的方案数,轮廓线 DP :\(f[i][j][S][k][h]\) 表示 DP 到了第 \(i\) 行第 \(j\) 格,轮廓线上 \(m\) 格的状态为 \(S\) 的方案数,\(k\) 表示最大的 \(x\) 使得模板矩阵第一行的前 \(x\) 个字符和目标矩阵第 \(i-1\) 行的后 \(x\) 个字符相同,\(h\) 表示最大的 \(x\) 使得模板矩阵第二行的前 \(x\) 个字符和目标矩阵第 \(i\) 行的后 \(x\) 个字符相同

  • 于是我们每次枚举下一个格子填什么字符,\(k\) 和 \(h\) 利用 KMP 进行转移,任何时候保证 \(k\) 和 \(h\) 不全为 \(c\) 即可

  • 但是这样做的复杂度为 \(O(qnmc^23^m)\) ,无法通过此题

  • 我们发现复杂度主要消耗在 \(3^m\) 上,考虑如何才能不储存前面每个字符的状态

  • 考虑仍然逐行转移 \(f[i][S]\) 表示前 \(i\) 行状态为 \(S\) ,但这里的 \(S\) 是一个集合,表示如果第 \(i\) 行以第 \(x\) 个格子为结尾的长度为 \(c\) 的子串与模板矩阵第一行相等,那么 \(x\in S\) ,否则 \(x\notin S\)

  • 然后每一列内逐格转移:\(g[j][S][k][h]\) 表示前 \(j\) 个格子,轮廓线上一排格子状态为 \(S\) ,并且前 \(j\) 个格子的后缀与模板矩阵第一行和第二行前缀匹配的最大长度分别为 \(k\) 和 \(h\) 的方案数

  • 边界 \(g[0][S][0][0]=f[i-1][S]\)

  • 还是每次枚举下一个格子填什么,注意若 \(g[j][S][k][h]\) 满足 \(j+1\in S\) 且 \(h\) 下一步转移到了 \(c\) 则此步不能转移,若 \(k\) 下一步转移到了 \(c\) 则转移后的 \(S\) 集合中包含 \(j+1\) ,否则不包含

  • 由于 \(S\) 中的任意元素都在 \([c,m]\) 内,故复杂度 \(O(qnmc^22^{m-c})\) ,可以通过此题

Code

#include <bits/stdc++.h>

const int N = 105, E = 14, C = (1 << 12) + 5, R = 8, rqy = 1e9 + 7;
const char ch[] = {'W', 'B', 'X'}; int n, m, c, q, Cm, f[N][C], g[E][C][R][R], nxt[2][R], tr[2][R][3], sum = 1;
char s[2][R]; void KMP(int n, char *s, int *nxt)
{
nxt[1] = 0;
for (int i = 2, j = 0; i <= n; i++)
{
while (j && s[i] != s[j + 1]) j = nxt[j];
if (s[i] == s[j + 1]) j++;
nxt[i] = j;
}
} inline void add(int &a, const int &b)
{
a += b; if (a >= rqy) a -= rqy;
} int DP()
{
f[0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
for (int S = 0; S < Cm; S++)
for (int k = 0; k <= c; k++)
for (int h = 0; h <= c; h++)
g[j][S][k][h] = 0;
for (int S = 0; S < Cm; S++)
g[0][S][0][0] = f[i - 1][S];
for (int j = 0; j < m; j++)
for (int S = 0; S < Cm; S++)
for (int k = 0; k <= c; k++)
for (int h = 0; h <= c; h++)
{
if (!g[j][S][k][h]) continue;
for (int w = 0; w < 3; w++)
{
int T = S;
if (j >= c - 1)
{
if (((S >> j - c + 1) & 1) && tr[1][h][w] == c)
continue;
T &= Cm - 1 ^ (1 << j - c + 1);
if (tr[0][k][w] == c) T |= 1 << j - c + 1;
}
add(g[j + 1][T][tr[0][k][w]][tr[1][h][w]], g[j][S][k][h]);
}
}
for (int S = 0; S < Cm; S++)
for (int k = 0; k <= c; k++)
for (int h = 0; h <= c; h++)
add(f[i][S], g[m][S][k][h]);
}
int res = 0;
for (int S = 0; S < Cm; S++) add(res, f[n][S]);
return res;
} int main()
{
std::cin >> n >> m >> c >> q; Cm = 1 << m - c + 1;
for (int i = 1; i <= n * m; i++) sum = 3ll * sum % rqy;
while (q--)
{
for (int i = 0; i < 2; i++) scanf("%s", s[i] + 1);
KMP(c, s[0], nxt[0]); KMP(c, s[1], nxt[1]);
memset(f, 0, sizeof(f));
for (int T = 0; T < 2; T++)
for (int i = 0; i <= c; i++)
for (int w = 0; w < 3; w++)
{
int j = i;
while (j && s[T][j + 1] != ch[w]) j = nxt[T][j];
tr[T][i][w] = s[T][j + 1] == ch[w] ? j + 1 : j;
}
printf("%d\n", (sum - DP() + rqy) % rqy);
}
return 0;
}

[LOJ#2017][轮廓线DP][KMP]「SCOI2016」围棋的更多相关文章

  1. 「SCOI2016」围棋 解题报告

    「SCOI2016」围棋 打CF后困不拉基的,搞了一上午... 考虑直接状压棋子,然后发现会t 考虑我们需要上一行的状态本质上是某个位置为末尾是否可以匹配第一行的串 于是状态可以\(2^m\)压住了, ...

  2. 【LOJ】#2017. 「SCOI2016」围棋

    题解 考虑到状态数比较复杂,其实我们需要轮廓线dp-- 我们设置\(f[x][y][S][h][k]\)为考虑到第(x,y)个格子,S是轮廓线上的匹配状态,是二进制,如果一位是1表示这一位匹配第一行匹 ...

  3. loj#2013. 「SCOI2016」幸运数字 点分治/线性基

    题目链接 loj#2013. 「SCOI2016」幸运数字 题解 和树上路径有管...点分治吧 把询问挂到点上 求出重心后,求出重心到每个点路径上的数的线性基 对于重心为lca的合并寻味,否则标记下传 ...

  4. loj#2015. 「SCOI2016」妖怪 凸函数/三分

    题目链接 loj#2015. 「SCOI2016」妖怪 题解 对于每一项展开 的到\(atk+\frac{dnf}{b}a + dnf + \frac{atk}{a} b\) 令$T = \frac{ ...

  5. loj#2016. 「SCOI2016」美味

    题目链接 loj#2016. 「SCOI2016」美味 题解 对于不带x的怎么做....可持久化trie树 对于带x,和trie树一样贪心 对于答案的二进制位,从高往低位贪心, 二进制可以表示所有的数 ...

  6. loj#2012. 「SCOI2016」背单词

    题目链接 loj#2012. 「SCOI2016」背单词 题解 题面描述有点不清楚. 考虑贪心 type1的花费一定不会是优的,不考虑, 所以先把后缀填进去,对于反串建trie树, 先填父亲再填儿子, ...

  7. loj #2013. 「SCOI2016」幸运数字

    #2013. 「SCOI2016」幸运数字 题目描述 A 国共有 n nn 座城市,这些城市由 n−1 n - 1n−1 条道路相连,使得任意两座城市可以互达,且路径唯一.每座城市都有一个幸运数字,以 ...

  8. 「SCOI2016」妖怪 解题报告

    「SCOI2016」妖怪 玄妙...盲猜一个结论,然后过了,事后一证,然后假了,数据真水 首先要最小化 \[ \max_{i=1}^n (1+k)x_i+(1+\frac{1}{k})y_i \] \ ...

  9. 「SCOI2016」美味 解题报告

    「SCOI2016」美味 状态极差无比,一个锤子题目而已 考虑每次对\(b\)和\(d\)求\(c=d \ xor \ (a+b)\)的最大值,因为异或每一位是独立的,所以我们可以尝试按位贪心. 如果 ...

随机推荐

  1. 超容易理解的call()、apply()、bind()的区别

    call().apply().bind()是用来改变this的指向的. 一 举个例子 一个叫喵喵的猫喜欢吃鱼,一个叫汪汪的小狗喜欢啃骨头,用代码实现如下: 有一天,小狗汪汪和喵喵共进午餐的时候,汪汪说 ...

  2. dotnet 判断程序当前使用管理员运行降低权使用普通权限运行

    有一些程序是不想通过管理员权限运行的,因为在很多文件的读写,如果用了管理员权限程序写入的程序,其他普通权限的程序是无法直接访问的.本文告诉大家如何判断当前的程序是通过管理员权限运行,然后通过资源管理器 ...

  3. 托管exe文件的加载和执行

    托管exe文件被启动的时候,首先被PE Loader载入.PE Loader载入exe文件之后,会分析PE文件头的data directory table,如果CLR_Header内的值不为0,表示该 ...

  4. 曹工说Spring Boot源码(6)-- Spring怎么从xml文件里解析bean的

    写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...

  5. mysql中information_schema.triggers字段说明

    1. 获取所有触发器信息(TRIGGERS) SELECT  *  FROM information_schema.TRIGGERS WHERE  TRIGGER_SCHEMA='数据库名';  TR ...

  6. ELK学习实验006:Nginx的日志分析系统之filebeat配置

    一 Filebeat工作原理 Filebeat由两个主要组件组成: prospector和 harvester 1.1 harvester 负责读取单个文件的内容 如果文件在读取时被制除或重命名, F ...

  7. 洛谷$ P$4317 花神的数论题 数位$dp$

    正解:数位$dp$ 解题报告: 传送门! 开始看到感觉有些新奇鸭,仔细一想发现还是个板子鸭,,, 考虑设$f_{i}$表示$sum[j]=i$的$j$的个数 日常考虑$dfs$呗,考虑变量要设哪些$Q ...

  8. 使用Django进行后端开发

    目录 更换pip源 搭建虚拟环境 项目目录设置 项目相关配置 更换pip源 更换pip源 搭建虚拟环境 搭建虚拟开发环境 项目目录设置 项目目录设置 项目相关配置 项目相关配置

  9. Tomcat启动失败原因

    Tomcat启动失败原因 一.Tomcat启动时,要求被加载的项目必须拥有独立路径名称 发布的两个工程具有相同路径名称,这是不允许. 二.8080端口号已经被占用了 启动失败原因,就是8085端口上, ...

  10. Eclipse和Tomcat的版本问题---已解决

    Eclipse和Tomcat的版本问题---已解决 这篇文章主要是解决版本匹配的问题 我的电脑上装的是jdk10,如图: Tomcat装的是9: 接着配置好环境变量,直接上图: 然后启功Tomcat, ...