• 题目传送门

  • 看到 \(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. 【b503】篝火晚会

    Time Limit: 1 second Memory Limit: 50 MB [问题描述] 佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官" ...

  2. 装饰器&偏函数与作用域与异常处理与文件读写

    装饰器 概念:是一个闭包,把一个函数当做参数返回一个替代版的函数,本质上就是一个返回函数的函数 简单的装饰器 def func1(): print("sunck is a good man& ...

  3. 【u033】地震逃生

    Time Limit: 1 second Memory Limit: 64 MB [问题描述] 汶川地震发生时,四川**中学正在上课,一看地震发生,老师们立刻带领x名学生逃跑,整个学校可以抽象地看成一 ...

  4. vscode 添加golang插件

    安装好git 下列命令中的路径一定要按照自己实际的路径来 mkdir -p $GOPATH/src/golang.org/x  //路径下创建此文件cd $GOPATH/src/golang.org/ ...

  5. Java程序员必备:异常的十个关键知识点

    前言 总结了Java异常十个关键知识点,面试或者工作中都有用哦,加油. 一. 异常是什么 异常是指阻止当前方法或作用域继续执行的问题.比如你读取的文件不存在,数组越界,进行除法时,除数为0等都会导致异 ...

  6. DEVOPS技术实践_16:使用Centos容器作为salve的报错offline的问题

    上一篇创建了一个centos的容器,而且已经安装了openssh [root@node6 ~]# docker ps -a f2320c5d3c54 centos minutes ago Exited ...

  7. lua字符串分割函数[适配中文特殊符号混合]

    lua的官方函数里无字符串分割,起初写了个简单的,随之发现如果是中文.字符串.特殊符号就会出现分割错误的情况,所以就有了这个zsplit. function zsplit(strn, chars) f ...

  8. lnmp安装部署-mysql5.6+centos6.8+php7.1+nginx1.9

    1.准备工作: 1)把所有的软件安装在/Data/apps/,源码包放在/Data/tgz/,数据放在/Data/data,日志文件放在/Data/logs,项目放在/Data/webapps, mk ...

  9. 3.24 7.13 Python基础汇总

    对象类型 类型名称 示例 简要说明 备注 数字 int,float,complex 1234,3.14,1.3e5,3+4j 数字大小没有限制 十六进制用0x前缀和0-9,a-f表示 字符串 str ...

  10. 推荐中的多任务学习-YouTube视频推荐

    本文将介绍Google发表在RecSys'19 的论文<Recommending What Video to Watch Next: A Multitask Ranking System> ...