「TJOI 2018」游园会 Party
「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的更多相关文章
- 「TJOI 2018」教科书般的亵渎
「TJOI 2018」教科书般的亵渎 题目描述 小豆喜欢玩游戏,现在他在玩一个游戏遇到这样的场面,每个怪的血量为 \(a_i\) ,且每个怪物血量均不相同, 小豆手里有无限张"亵渎" ...
- LOJ #2542. 「PKUWC 2018」随机游走(最值反演 + 树上期望dp + FMT)
写在这道题前面 : 网上的一些题解都不讲那个系数是怎么推得真的不良心 TAT (不是每个人都有那么厉害啊 , 我好菜啊) 而且 LOJ 过的代码千篇一律 ... 那个系数根本看不出来是什么啊 TAT ...
- LOJ #2802. 「CCC 2018」平衡树(整除分块 + dp)
题面 LOJ #2802. 「CCC 2018」平衡树 题面有点难看...请认真阅读理解题意. 转化后就是,给你一个数 \(N\) ,每次选择一个 \(k \in [2, N]\) 将 \(N\) 变 ...
- LOJ #2541. 「PKUWC 2018」猎人杀(容斥 , 期望dp , NTT优化)
题意 LOJ #2541. 「PKUWC 2018」猎人杀 题解 一道及其巧妙的题 , 参考了一下这位大佬的博客 ... 令 \(\displaystyle A = \sum_{i=1}^{n} w_ ...
- LOJ #2540. 「PKUWC 2018」随机算法(概率dp)
题意 LOJ #2540. 「PKUWC 2018」随机算法 题解 朴素的就是 \(O(n3^n)\) dp 写了一下有 \(50pts\) ... 大概就是每个点有三个状态 , 考虑了但不在独立集中 ...
- LOJ #2538. 「PKUWC 2018」Slay the Spire (期望dp)
Update on 1.5 学了 zhou888 的写法,真是又短又快. 并且空间是 \(O(n)\) 的,速度十分优秀. 题意 LOJ #2538. 「PKUWC 2018」Slay the Spi ...
- loj#2054. 「TJOI / HEOI2016」树
题目链接 loj#2054. 「TJOI / HEOI2016」树 题解 每次标记覆盖整棵字数,子树维护对于标记深度取max dfs序+线段树维护一下 代码 #include<cstdio> ...
- 「TJOI / HEOI2016」字符串
「TJOI / HEOI2016」字符串 题目描述 佳媛姐姐过生日的时候,她的小伙伴从某东上买了一个生日礼物.生日礼物放在一个神奇的箱子中.箱子外边写了一个长为 \(n\) 的字符串 \(s\),和 ...
- AC日记——#2054. 「TJOI / HEOI2016」树
#2054. 「TJOI / HEOI2016」树 思路: 线段树: 代码: #include <cstdio> #include <cstring> #include < ...
随机推荐
- IO流-文件的写入和读取
1.文件写入 类: FileWriter继承自Writer(字符流基类之一,另外一个为Reader) 方法: writer(参数); 根据参数可以写入字符.字符数组.字符数组中的一部分.整型.字符串. ...
- UIWebView---iOS-Apple苹果官方文档翻译
CHENYILONG Blog UIWebView---iOS-Apple苹果官方文档翻译 UIWebView 技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博h ...
- iOS网络基础---iOS-Apple苹果官方文档翻译
CHENYILONG Blog iOS网络基础---iOS-Apple苹果官方文档翻译 iOS网络基础 技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博http: ...
- Linux CGI编程基础【整理】
Linux CGI编程基础 1.为什么使用CGI? 如前面所见,任何的HTML均是静态网页,它无法实现一些复杂的功能,而CGI可以为我们实现.如:a.列出服务器上某个目录中的文件,对目录中的文件进行操 ...
- hdu 2852 KiKi's K-Number (线段树)
版权声明:本文为博主原创文章,未经博主允许不得转载. hdu 2852 题意: 一个容器,三种操作: (1) 加入一个数 e (2) 删除一个数 e,如果不存在则输出 No Elment! (3) 查 ...
- Centos查看端口占用和关闭端口
Centos查看端口占用情况命令,比如查看80端口占用情况使用如下命令: lsof -i tcp:80 列出所有端口 netstat -ntlp 1.开启端口(以80端口为例) ...
- Python中的raw_input()和input()
raw_input()和input()都是python中的内建函数,用于读取控制台用户的输入,但有所区别: [nr@localhost conf]$ python Python 2.7.5 (defa ...
- Python+Selenium 自动化实现实例-获取测试对象的Css属性
#coding:utf-8 '''获取测试对象的css属性场景 当你的测试用例纠结细枝末节的时候,你就需要通过判断元素的css属性来验证你的操作是否达到了预期的效果.比如你可以通过判断页面上的标题字号 ...
- SG函数(转自百度百科)
给定一个有向无环图和一个起始顶点上的一枚棋子,两名选手交替的将这枚棋子沿有向边进行移动,无法移 动者判负.事实上,这个游戏可以认为是所有Impartial Combinatorial Games的抽象 ...
- find tar排除指定文件或目录操作及查找文件内容关键字
1.find查找排除单个目录 查找当前目录或者子目录下所有.txt文件,但是跳过子目录sk find . -path "./sk" -prune -o -name "*. ...