「TJOI 2018」游园会 Party

题目描述

小豆参加了 \(NOI\) 的游园会,会场上每完成一个项目就会获得一个奖章,奖章只会是 \(N, O, I\) 的字样。 在会场上他收集到了 \(K\) 个奖章组成的串。兑奖规则是奖章串和兑奖串的最长公共子序列长度为小豆最后奖励的等级。 现在已知兑奖串长度为 \(N\) ,并且在兑奖串上不会出现连续三个奖章为 NOI ,即奖章中不会出现子串 NOI 。 现在小豆想知道各个奖励等级会对应多少个不同的合法兑奖串。

\(N \leq 1000, K \leq 15\)

解题思路 :

首先吐槽一下这个题和 「BZOJ 3864」 几乎没有任何区别,省选放这种题真的合适吗?

考虑一个比较 \(naive\) 的做法,用一个想当然的 \(dp\) 来对答案计数

\(f[i][j][s1][s2]\) 表示长度为 \(i\) 的兑奖串与奖章串的 \(lcs = j\) 且 \(i-1\) 上的字母为 \(s1\) ,\(i\) 上的字母为 \(s2\) 的方案数

观察发现,这个东西转移需要知道与奖章串每一个前缀的 \(lcs\) 是多少,不然 \(i+1\) 的时候无法转移

于是就要维护当前的一个状态集合 \(S\) ,\(S_j\) 表示奖章串前缀 \(j\) 与当且兑奖串的 \(lcs\)

可以对 \(S\) 做一遍 \(lcs\) 的 \(dp\) 从 \(f[i][S]\) 转移到 \(f[i+1][S']\) ,但是 \(S\) 有至多 \(15\) 位,每一位的值至多为 \(15\)

显然无法直接表示为状态,于是需要进行简化状态.

观察发现,对于任意一个状态集合 \(S\), \(S_j -S_{j-1} \leq 1\)

不妨对 \(S\) 进行差分,那么每一位的值域范围就是 \([0,1]\) 了,可以用一个至多 \(2^{15}\) 的数来表示

于是只需要用类似于 \(lcs\) 的 \(dp\) 预处理出来 \(S\) 之间的转移,并额外用一个 \(dp\) 对答案计数即可

\(f[i][S][s1][s2]\) 表示长度为 \(i\) 的兑奖串,与奖章串的每一个前缀的 \(lcs\) 状态集合为 \(S\), 且 \(i-1\) 上的字母为 \(s1\) ,\(i\) 上的字母为 \(s2\) 的方案数

设 \(S'\) 为 \(S\) 能转移到的状态,判一下新加的字符是否会和 \(s1, s2\) 形成 \(NOI\) 再转移即可

预处理的复杂度是 \(O(2^k \times k)\) ,\(dp\) 的复杂度是 \(O(n \times 2^k)\),总复杂度是 \(O(n \times 2^k)\)

/*program by mangoyang*/
#include<bits/stdc++.h>
#define inf (0x7f7f7f7f)
#define Max(a, b) ((a) > (b) ? (a) : (b))
#define Min(a, b) ((a) < (b) ? (a) : (b))
typedef long long ll;
using namespace std;
template <class T>
inline void read(T &x){
int f = 0, ch = 0; x = 0;
for(; !isdigit(ch); ch = getchar()) if(ch == '-') f = 1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
if(f) x = -x;
}
const int N = 1005, K = (1 << 15) + 10, Mod = 1000000007;
char str[N];
int f[2][K][3][3], a[N], s[N], Ans[N], n, m, all; inline void up(int &x, int y){ x += y; if(x >= Mod) x %= Mod; } struct Node{ int x, s1, s2; }; vector<Node> g[K][3][3];
namespace Prework{
inline void GetTrans(int x){
for(int i = 0; i < m; i++)
if((1 << i) & x) a[i+1] = a[i] + 1; else a[i+1] = a[i];
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
for(int k = 0; k < 3; k++)
if(i != 0 || j != 1 || k != 2){
int res = 0;
for(int p = 1, ls = 0, now; p <= m; p++, ls = now){
now = Max(a[p], a[p-1] + (s[p] == k));
if(now > ls) res |= (1 << p - 1); else now = ls;
}
g[x][i][j].push_back((Node){res, j, k});
}
}
inline void realmain(){ for(int i = 0; i <= all; i++) GetTrans(i); }
} int main(){
read(n), read(m), all = (1 << m) - 1;
scanf("%s", str + 1);
for(int i = 1; i <= m; i++){
if(str[i] == 'N') s[i] = 0;
if(str[i] == 'O') s[i] = 1;
if(str[i] == 'I') s[i] = 2;
}
Prework::realmain();
f[0][0][2][2] = 1;
for(int i = 0, o = 0; i < n; i++, o ^= 1){
memset(f[o^1], 0, sizeof(f[o^1]));
for(int j = 0; j <= all; j++)
for(int s1 = 0; s1 < 3; s1++)
for(int s2 = 0; s2 < 3; s2++)
for(int k = 0; k < g[j][s1][s2].size(); k++){
Node now = g[j][s1][s2][k];
int nxt = now.x, s3 = now.s1, s4 = now.s2;
up(f[o^1][nxt][s3][s4], f[o][j][s1][s2]);
}
} for(int j = 0; j <= all; j++){
int tot = 0;
for(int i = 0; i < m; i++) tot += (j >> i) & 1;
for(int s1 = 0; s1 < 3; s1++)
for(int s2 = 0; s2 < 3; s2++) up(Ans[tot], f[n&1][j][s1][s2]);
}
for(int i = 0; i <= m; i++) cout << Ans[i] << endl;
return 0;
}

「TJOI 2018」游园会 Party的更多相关文章

  1. 「TJOI 2018」教科书般的亵渎

    「TJOI 2018」教科书般的亵渎 题目描述 小豆喜欢玩游戏,现在他在玩一个游戏遇到这样的场面,每个怪的血量为 \(a_i\) ,且每个怪物血量均不相同, 小豆手里有无限张"亵渎" ...

  2. LOJ #2542. 「PKUWC 2018」随机游走(最值反演 + 树上期望dp + FMT)

    写在这道题前面 : 网上的一些题解都不讲那个系数是怎么推得真的不良心 TAT (不是每个人都有那么厉害啊 , 我好菜啊) 而且 LOJ 过的代码千篇一律 ... 那个系数根本看不出来是什么啊 TAT ...

  3. LOJ #2802. 「CCC 2018」平衡树(整除分块 + dp)

    题面 LOJ #2802. 「CCC 2018」平衡树 题面有点难看...请认真阅读理解题意. 转化后就是,给你一个数 \(N\) ,每次选择一个 \(k \in [2, N]\) 将 \(N\) 变 ...

  4. LOJ #2541. 「PKUWC 2018」猎人杀(容斥 , 期望dp , NTT优化)

    题意 LOJ #2541. 「PKUWC 2018」猎人杀 题解 一道及其巧妙的题 , 参考了一下这位大佬的博客 ... 令 \(\displaystyle A = \sum_{i=1}^{n} w_ ...

  5. LOJ #2540. 「PKUWC 2018」随机算法(概率dp)

    题意 LOJ #2540. 「PKUWC 2018」随机算法 题解 朴素的就是 \(O(n3^n)\) dp 写了一下有 \(50pts\) ... 大概就是每个点有三个状态 , 考虑了但不在独立集中 ...

  6. LOJ #2538. 「PKUWC 2018」Slay the Spire (期望dp)

    Update on 1.5 学了 zhou888 的写法,真是又短又快. 并且空间是 \(O(n)\) 的,速度十分优秀. 题意 LOJ #2538. 「PKUWC 2018」Slay the Spi ...

  7. loj#2054. 「TJOI / HEOI2016」树

    题目链接 loj#2054. 「TJOI / HEOI2016」树 题解 每次标记覆盖整棵字数,子树维护对于标记深度取max dfs序+线段树维护一下 代码 #include<cstdio> ...

  8. 「TJOI / HEOI2016」字符串

    「TJOI / HEOI2016」字符串 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一个长为 \(n\) 的字符串 \(s\),和 ...

  9. AC日记——#2054. 「TJOI / HEOI2016」树

    #2054. 「TJOI / HEOI2016」树 思路: 线段树: 代码: #include <cstdio> #include <cstring> #include < ...

随机推荐

  1. 【CodeForces】915 D. Almost Acyclic Graph 拓扑排序找环

    [题目]D. Almost Acyclic Graph [题意]给定n个点的有向图(无重边),问能否删除一条边使得全图无环.n<=500,m<=10^5. [算法]拓扑排序 [题解]找到一 ...

  2. 2017 ACM暑期多校联合训练 - Team 4 1012 HDU 6078 Wavel Sequence (模拟)

    题目链接 Problem Description Have you ever seen the wave? It's a wonderful view of nature. Little Q is a ...

  3. python3爬虫.1.简单的网页爬虫

    此为记录下我自己的爬虫学习过程. 利用url包抓取网页 import urllib.request #url包 def main(): url = "http://www.douban.co ...

  4. 统计oracle表中字段的个数

    select count(column_name) from user_tab_columns where table_name='emp' dba权限对应的视图是dba_tab_columns 和a ...

  5. PHP深浅拷贝

    举个栗子: <?php class Example1 { public $name; public function __construct($name) { $this->name = ...

  6. MySQL之——如何添加新数据库到MySQL主从复制列表 【转】

    转自 转载请注明出处:http://blog.csdn.net/l1028386804/article/details/54653691 MySQL主从复制一般情况下我们会设置需要同步的数据库,使用参 ...

  7. 144.Binary Tree Preorder Traversal---二叉树先序、中序非递归遍历

    题目链接 题目大意:返回二叉树的先序遍历list.中序见94,后序见145. 法一:普通递归遍历,只是这里多了一个list数组,所以分成了两个函数.代码如下(耗时1ms): public List&l ...

  8. 苹果笔记本MacBookPro 的新手使用技巧

    Mac 系统的桌面 Mac 的桌面是一个很炫的3D, 背景是一张“星空”图 Dock:  在桌面的下方,有一排图标, 这个叫Dock, 用来快速启动程序, 进入文件夹, 它同时还可以停靠正在运行的程序 ...

  9. PyQt实现测试工具

    测试工具: 1. 基本界面实现: # coding:utf-8 import sys import os import os.path import re import time from PyQt4 ...

  10. c++输出保留固定小数位数

    cout<<setprecision(6)<<fixed<<ans<<endl;