[LOJ#2017][轮廓线DP][KMP]「SCOI2016」围棋
看到 \(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」围棋的更多相关文章
- 「SCOI2016」围棋 解题报告
「SCOI2016」围棋 打CF后困不拉基的,搞了一上午... 考虑直接状压棋子,然后发现会t 考虑我们需要上一行的状态本质上是某个位置为末尾是否可以匹配第一行的串 于是状态可以\(2^m\)压住了, ...
- 【LOJ】#2017. 「SCOI2016」围棋
题解 考虑到状态数比较复杂,其实我们需要轮廓线dp-- 我们设置\(f[x][y][S][h][k]\)为考虑到第(x,y)个格子,S是轮廓线上的匹配状态,是二进制,如果一位是1表示这一位匹配第一行匹 ...
- loj#2013. 「SCOI2016」幸运数字 点分治/线性基
题目链接 loj#2013. 「SCOI2016」幸运数字 题解 和树上路径有管...点分治吧 把询问挂到点上 求出重心后,求出重心到每个点路径上的数的线性基 对于重心为lca的合并寻味,否则标记下传 ...
- loj#2015. 「SCOI2016」妖怪 凸函数/三分
题目链接 loj#2015. 「SCOI2016」妖怪 题解 对于每一项展开 的到\(atk+\frac{dnf}{b}a + dnf + \frac{atk}{a} b\) 令$T = \frac{ ...
- loj#2016. 「SCOI2016」美味
题目链接 loj#2016. 「SCOI2016」美味 题解 对于不带x的怎么做....可持久化trie树 对于带x,和trie树一样贪心 对于答案的二进制位,从高往低位贪心, 二进制可以表示所有的数 ...
- loj#2012. 「SCOI2016」背单词
题目链接 loj#2012. 「SCOI2016」背单词 题解 题面描述有点不清楚. 考虑贪心 type1的花费一定不会是优的,不考虑, 所以先把后缀填进去,对于反串建trie树, 先填父亲再填儿子, ...
- loj #2013. 「SCOI2016」幸运数字
#2013. 「SCOI2016」幸运数字 题目描述 A 国共有 n nn 座城市,这些城市由 n−1 n - 1n−1 条道路相连,使得任意两座城市可以互达,且路径唯一.每座城市都有一个幸运数字,以 ...
- 「SCOI2016」妖怪 解题报告
「SCOI2016」妖怪 玄妙...盲猜一个结论,然后过了,事后一证,然后假了,数据真水 首先要最小化 \[ \max_{i=1}^n (1+k)x_i+(1+\frac{1}{k})y_i \] \ ...
- 「SCOI2016」美味 解题报告
「SCOI2016」美味 状态极差无比,一个锤子题目而已 考虑每次对\(b\)和\(d\)求\(c=d \ xor \ (a+b)\)的最大值,因为异或每一位是独立的,所以我们可以尝试按位贪心. 如果 ...
随机推荐
- 转 最近5年183个Java面试问题列表及答案[最全]
Java 面试随着时间的改变而改变.在过去的日子里,当你知道 String 和 StringBuilder 的区别(String 类型和 StringBuffer 类型的主要性能区别其实在于 Stri ...
- dotnet Framework 源代码 类库的意思
本文告诉大家 dotnet framework 的源代码类库的意思 下面列出来 dotnet framework 源代码的各个类库的作用. System System 命名空间包含基本类和基类,这些类 ...
- 牛客练习赛11 假的字符串 (Trie树+拓扑找环)
牛客练习赛11 假的字符串 (Trie树+拓扑找环) 链接:https://ac.nowcoder.com/acm/problem/15049 来源:牛客网 给定n个字符串,互不相等,你可以任意指定字 ...
- filter 开发
在filter中可以得到代表用户请求和响应的request.response对象,因此在编程中可以使用Decorator(装饰器)模式对request.response对象进行包装,再把包装对象传给目 ...
- Django学习小记1-安装配置
Django是一个开放源代码的Web应用框架,由Python写成. python 中的web框架有许多例如:Django.Tornado.Flask..而Django相较与其他WEB框架其优势为:大而 ...
- knn识别简单验证码
参考 https://www.biaodianfu.com/knn-captcha-recognition.html 内容大致一样,只是根据自己的想法加入了一些改动 KNN(k近邻算法) 算法原理请看 ...
- js获取当前农历时间
<template> <div class="gaia-header"> <img alt="gaia_logo" src=&qu ...
- Markdown数学符号
上标 语法: x^2 效果: \(x^2\) 下标 语法: x_i 效果: \(x_i\) 整体 语法: x^{2y} 效果: \(x^{2y}\) 大括号 语法: \{\} 效果: \(\{\}\) ...
- 使用Theia——创建插件
上一篇:使用Theia——创建扩展包 创建Theia插件 下面我们来看看如何创建Theia插件.作为示例,我们将注册一个Hello World命令,该命令显示一个“Hello World”通知.本文将 ...
- 小白学 Python 爬虫(34):爬虫框架 Scrapy 入门基础(二)
人生苦短,我用 Python 前文传送门: 小白学 Python 爬虫(1):开篇 小白学 Python 爬虫(2):前置准备(一)基本类库的安装 小白学 Python 爬虫(3):前置准备(二)Li ...