[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)\)的最大值,因为异或每一位是独立的,所以我们可以尝试按位贪心. 如果 ...
随机推荐
- vue-learning:19 - js - filters
filters 基本使用 仅限在插值{{}}和v-bind指令中使用 管道符|分隔 链式调用 传入参数 全局注册和局部注册 纯函数性质(不能使用this) 基本使用 我们看下之前用计算属性实现的例子, ...
- Linux 内核驱动支持什么设备
struct usb_device_id 结构提供了这个驱动支持的一个不同类型 USB 设备的列表. 这个 列表被 USB 核心用来决定给设备哪个驱动, 并且通过热插拔脚本来决定哪个驱动自动加载, 当 ...
- python中交换两个变量值的方法
a = 4b = 5 #第1种c = 0c = aa = bb = c #第2种a = a+bb = a-ba = a-b #第3种a,b = b,a print("a=%d,b=%d&qu ...
- 想突破学习瓶颈,为什么要认真的学一下Dubbo?
今天有学生在问,在学习dubbo的时候遇到瓶颈了怎么办,一些东西就感觉就在那里,但是,就是碰不到,摸不着,陷入了迷茫,今天在这里,就跟大家讲一下怎么突破这个瓶颈 先自我介绍一下哈,我是鲁班学院的周瑜老 ...
- jquery $.post()返回数据
javawe项目很多情况下需要通过$.post()进行前端和后端传递数据 格式是: $.post(url,data,function(result,statue){ alert(result); }, ...
- 登录密码忘记修改jenkins
find / -type f -name 'config.xml' 然后需要删除config.xml文件中的以下内容: <useSecurity>true</useSecurity& ...
- 1062 最简分数 (20分)C语言
一个分数一般写成两个整数相除的形式:N/M,其中 M 不为0.最简分数是指分子和分母没有公约数的分数表示形式. 现给定两个不相等的正分数 N1/M1和 N2/M2,要求你按从小到大的顺序列出它们之间 ...
- 那天晚上和@FeignClient注解的深度交流
废话篇 那晚,我和@FeignClient注解的深度交流了一次,爽! 主要还是在技术群里看到有同学在问相关问题,比如: contextId是干嘛的?name相同的多个Client会报错? 然后觉得有必 ...
- golang编译之vendor机制
Go 1.5引入了vendor 机制,但是需要手动设置环境变量 GO15VENDOREXPERIMENT= 1,Go编译器才能启用.从Go1.6起,,默认开启 vendor 目录查找,vendor 机 ...
- 侠说java8-行为参数化(开山篇)
啥是行为参数化 行为参数化的本质是不执行复杂的代码块,让逻辑清晰可用. 相信使用过js的你肯定知道,js是可以传递函数的,而在 java中也有类似的特性,那就是匿名函数. 理解:行为参数化是一种方法, ...