luoguP5108 仰望半月的夜空 [官方?]题解 后缀数组 / 后缀树 / 后缀自动机 + 线段树 / st表 + 二分
仰望半月的夜空 题解
可以的话,支持一下原作吧...
这道题数据很弱.....
因此各种乱搞估计都是能过的....
算法一
暴力长度然后判断判断,复杂度\(O(n^3)\)
期望得分15分
算法二
通过二分+\(hash\)或者等等来优化字典序比较,复杂度\(O(n^2 \log n)\),可能要松一下
期望得分30分
ps:好吧有55分...
算法三
我们考虑字符集非常小的情况
我们猜想出题人很难卡掉玄学做法,因此我们就想一个玄学做法
我们考虑用\(SAM\)来处理这个问题
建出\(SAM\)后,我们用\(dp\)预处理出一个点向后走最长能走多远
然后在\(SAM\)上根据字典序找到每个长度分别对应哪个集合
取对应集合的\(right\)集合中的最小值即可
复杂度是个玄学,但是能通过字符集很小和数据随机的点
期望得分55~75分
不会在字符集很小的情况下造数据,求大佬
算法四
注意到实际上复杂度很高是因为我们在一个\(DAG\)上寻找答案
实际上我们建出后缀树,然后在树上寻找对应集合即可
复杂度可以做到\(O(n \log n)\)
字符集较小的时候可以直接\(O(n)\)
期望得分100分
算法五
考虑用\(SA\)来解决这个问题
不难发现,给后缀排好序之后,按照长度维护一个单调栈
不妨设单调栈中的后缀分别为\(i_1, i_2, ..., i_p\)
那么,长度\(L(i_1) + 1 \sim L(i_2)\)对应的字典序最小的串一定是以\(i_2\)为开头的
这样,我们就可以找到这\(n\)个串
然后再用二分找到这个串在后缀数组中对应的区间,用线段树或者\(st\)表(暴力也能过)来维护\(sa\)的最小值即可
复杂度\(O(n \log n)\)
期望得分100分
几乎是个模板字符串吧.....
Q:前半段和后半段相等的串有啥用啊?
A:我也不知道,所以才放在那里的....
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define ll long long
#define ri register int
#define rep(io, st, ed) for(ri io = st; io <= ed; io ++)
#define drep(io, ed, st) for(ri io = ed; io >= st; io --)
#define gc getchar
inline int read() {
int p = 0, w = 1; char c = gc();
while(c > '9' || c < '0') { if(c == '-') w = -1; c = gc(); }
while(c >= '0' && c <= '9') p = p * 10 + c - '0', c = gc();
return p * w;
}
const int sid = 500050;
const int ssd = 2000050;
char t[sid];
int n, S, top, s[sid], T[sid];
int nc[sid], p1[sid], p2[sid], ip[sid];
int sa[sid], rk[sid], lg[sid], bit[sid], st[sid][20];
void init() {
rep(i, 0, 25) bit[i] = 1 << i;
rep(i, 2, 400000) lg[i] = lg[i >> 1] + 1;
}
void rsort(int n, int m) {
rep(i, 1, n) nc[p2[i]] ++;
rep(i, 1, m) nc[i] += nc[i - 1];
rep(i, 1, n) ip[nc[p2[i]] --] = i;
rep(i, 0, m) nc[i] = 0;
rep(i, 1, n) nc[p1[i]] ++;
rep(i, 1, m) nc[i] += nc[i - 1];
drep(i, n, 1) sa[nc[p1[ip[i]]] --] = ip[i];
rep(i, 0, m) nc[i] = 0;
}
void ssort() {
rep(i, 1, n) T[i] = s[i];
sort(T + 1, T + n + 1);
int m = unique(T + 1, T + n + 1) - T - 1;
rep(i, 1, n) rk[i] = lower_bound(T + 1, T + m + 1, s[i]) - T;
for(ri k = 1; k <= n; k <<= 1) {
rep(i, 1, n) {
p1[i] = rk[i];
p2[i] = (i + k <= n) ? rk[i + k] : 0;
}
rsort(n, m);
rk[sa[1]] = m = 1;
rep(i, 2, n) {
if(p1[sa[i]] == p1[sa[i - 1]] && p2[sa[i]] == p2[sa[i - 1]]) m --;
rk[sa[i]] = ++ m;
}
if(m >= n) break;
}
for(ri i = 1, k = 0; i <= n; i ++) {
if(k) k --;
int j = sa[rk[i] - 1];
while(s[i + k] == s[j + k]) k ++;
st[rk[i]][0] = k;
}
rep(j, 1, 19)
for(ri i = 1; i + bit[j] - 1 <= n; i ++)
st[i][j] = min(st[i][j - 1], st[i + bit[j - 1]][j - 1]);
}
inline int lcp(int l, int r) {
if(l == r) return n - sa[l] + 1;
if(l > r) swap(l, r); l ++;
int L = lg[r - l + 1];
return min(st[l][L], st[r - bit[L] + 1][L]);
}
int stk[sid], mi[ssd];
#define ls (o << 1)
#define rs (o << 1 | 1)
inline void build(int o, int l, int r) {
if(l == r) { mi[o] = sa[l]; return; }
int mid = (l + r) >> 1;
build(ls, l, mid);
build(rs, mid + 1, r);
mi[o] = min(mi[ls], mi[rs]);
}
inline int qry(int o, int l, int r, int ml, int mr) {
if(ml > r || mr < l) return 1e9;
if(ml <= l && mr >= r) return mi[o];
int mid = (l + r) >> 1;
return min(qry(ls, l, mid, ml, mr), qry(rs, mid + 1, r, ml, mr));
}
inline int get_pre(int l, int r, int pos, int L) {
int ret = -1;
while(l <= r) {
int mid = (l + r) >> 1;
if(lcp(mid, pos) >= L) r = mid - 1, ret = mid;
else l = mid + 1;
}
return ret;
}
inline int get_suf(int l, int r, int pos, int L) {
int ret = -1;
while(l <= r) {
int mid = (l + r) >> 1;
if(lcp(mid, pos) >= L) l = mid + 1, ret = mid;
else r = mid - 1;
}
return ret;
}
inline int solve(int l, int L) {
int pre = get_pre(1, l, l, L);
int suf = get_suf(l, n, l, L);
return qry(1, 1, n, pre, suf);
}
inline int L(int x) {
if(!x) return 0;
return n - sa[x] + 1;
}
int main() {
init();
S = read(); n = read();
if(S == 26) {
scanf("%s", t + 1);
rep(i, 1, n) s[i] = t[i];
}
else rep(i, 1, n) s[i] = read();
s[0] = s[n + 1] = -1;
ssort();
build(1, 1, n);
rep(i, 1, n)
if(L(i) > L(stk[top])) stk[++ top] = i;
rep(i, 1, top) {
int l = L(stk[i - 1]) + 1, r = L(stk[i]);
rep(j, l, r) printf("%d ", solve(stk[i], j));
}
printf("\n");
return 0;
}
luoguP5108 仰望半月的夜空 [官方?]题解 后缀数组 / 后缀树 / 后缀自动机 + 线段树 / st表 + 二分的更多相关文章
- [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...
- HDU - 6704 K-th occurrence (后缀数组+主席树/后缀自动机+线段树合并+倍增)
题意:给你一个长度为n的字符串和m组询问,每组询问给出l,r,k,求s[l,r]的第k次出现的左端点. 解法一: 求出后缀数组,按照排名建主席树,对于每组询问二分或倍增找出主席树上所对应的的左右端点, ...
- 洛谷P4493 [HAOI2018]字串覆盖(后缀自动机+线段树+倍增)
题面 传送门 题解 字符串就硬是要和数据结构结合在一起么--\(loj\)上\(rk1\)好像码了\(10k\)的样子-- 我们设\(L=r-l+1\) 首先可以发现对于\(T\)串一定是从左到右,能 ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- 洛谷P2178 [NOI2015]品酒大会(后缀自动机 线段树)
题意 题目链接 Sol 说一个后缀自动机+线段树的无脑做法 首先建出SAM,然后对parent树进行dp,维护最大次大值,最小次小值 显然一个串能更新答案的区间是\([len_{fa_{x}} + 1 ...
- BZOJ1396: 识别子串(后缀自动机 线段树)
题意 题目链接 Sol 后缀自动机+线段树 还是考虑通过每个前缀的后缀更新答案,首先出现次数只有一次,说明只有\(right\)集合大小为\(1\)的状态能对答案产生影响 设其结束位置为\(t\),代 ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
随机推荐
- git 配置 SSH密钥
1.登录用户 $ git config --global user.name "geekfeier" $ git config --global user.email " ...
- 关于new Handler()与new Handler(Looper.getMainLooper())区别
如果你不带参数的实例化:Handler handler=new Handler();那么这个会默认用当前线程的Looper对象. 一般而言,如果你的Handler是要用来刷新UI的,那么就需要在主线程 ...
- oracle 创建表空间 、用户 、赋权、建表
一.创建表空间 1.创建临时表空间 create temporary tablespace TS_TEM_TAB_SPACE tempfile 'D:\oracle\TS_TEM_TAB_SPACE. ...
- DevExpress 行事历(Scheduler)的常用属性、事件和方法
一.TcxScheduler[TcxScheduler常用属性]1.Storage - 邦定一个Storage为Scheduler显示提供数据 2.DateNavigate.ColCount ...
- Intel大坑之一:丢失的SSE2 128bit/64bit 位移指令,马航MH370??
缘由 最近在写一些字符串函数的优化,兴趣使然,可是写的过程中,想要实现 128bit 的按 bit 逻辑位移,遇到了一个大坑,且听我娓娓道来. 如果要追究标题,更确切的是丢失的SSE2 128 bit ...
- vue总结07 常用插件
插件 开发插件 插件通常会为 Vue 添加全局功能.插件的范围没有限制——一般有下面几种: 添加全局方法或者属性,如: vue-custom-element 添加全局资源:指令/过滤器/过渡等,如 v ...
- 【前端vue开发】vue子调父 $emit (把子组件的数据传给父组件)
ps:App.vue 父组件 Hello.vue 子组件 <!--App.vue :--> <template> <div id="app"> ...
- Python_oldboy_自动化运维之路(二)
本节内容: 1.pycharm工具的使用 2.进制运算 3.表达式if ...else语句 4.表达式for 循环 5.break and continue 6.表达式while 循环 1.pycha ...
- NYOJ 石子合并(一)(区间DP)
题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737 题目大意: 有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆 ...
- 20165203&20165206结对创意感想
一.结对学习过程 我和我的搭档性格志趣相投,而且各有所长,我们两个均属于一丝不苟的人,做一件事就要把它做好.因此,我们学习理念相同,志趣相投,这可能会占很大的优势.首先,我们会利用一周的前几天看课本, ...