Luogu4770 NOI2018 你的名字 SAM、主席树
UPD:发现之前被smy误导的一个细节,改过来之后就AC了……
一道比较套路的SAM题,虽然我连套路都不会……
先考虑前\(68pts\),也就是\(l=1 , r=|S|\)的情况。我们对\(S\)建好SAM,把\(T\)扔到\(S\)的SAM上匹配,如果不考虑本质不同子串的性质,那么答案就是\(\sum\limits_{i=1}^{|T|} i - l_i\),其中\(l_i\)是匹配到第\(i\)个字符时的匹配长度。
然后考虑如何去重。对\(T\)也建SAM,把\(T\)也放在\(T\)的SAM上匹配。发现在匹配到第\(i\)个字符时,以\(i\)为右端点、长度为\([1,l_i]\)的串都是不合法的,而这些不合法的串在\(T\)所在的SAM上对应的是\(parent\)树上的一条链。于是在放在\(T\)的SAM上匹配的时候不断跳父亲,直到\(Shortest_u \leq l_i\),然后在\(u\)上打上\(l_i\)的标记。最后在\(parent\)树上递推一遍把标记传一下就可以计算出不合法的串的数量。
然后考虑一般情况。唯一存在的问题是:可能存在某些情况下到达的状态在\(S[l,r]\)中没有出现。这个时候考虑对于\(S\)上的每一个节点维护它的\(endpos\)集合,此时\(S[l,r]\)中不存在当前对应的串\(\Leftrightarrow\)当前状态的\(endpos\)集合与\([l+len-1,r]\)无交,然后\(--len\),如果\(len =\)父亲状态的\(Longest\)就跳父亲。这里暴力修改\(len\)的总次数仍然是\(O(|T|)\)的所以可以接受。
对于\(endpos\)集合的维护,不难知道某一个点有的\(endpos\)它的祖先也会有。所以就变成了一个子树内某区间是否有值的问题,在\(parent\)树上跑出dfn序然后建立主席树即可。总复杂度\(O(\sum |T| log |S|)\)
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<vector>
//This code is written by Itst
using namespace std;
const int MAXN = 1e6 + 7;
namespace Tree{
struct node{
int l , r , sz;
}Tree[MAXN << 4];
int rt[MAXN] , cnt;
#define mid ((l + r) >> 1)
int insert(int x , int l , int r , int tar){
int t = ++cnt;
Tree[t] = Tree[x];
++Tree[t].sz;
if(l == r) return t;
if(mid >= tar)
Tree[t].l = insert(Tree[t].l , l , mid , tar);
else
Tree[t].r = insert(Tree[t].r , mid + 1 , r , tar);
return t;
}
bool query(int x , int y , int l , int r , int L , int R){
if(L > R || Tree[x].sz == Tree[y].sz) return 0;
if(l >= L && r <= R) return 1;
if(mid >= L && query(Tree[x].l , Tree[y].l , l , mid , L , R))
return 1;
return mid < R && query(Tree[x].r , Tree[y].r , mid + 1 , r , L , R);
}
}
using Tree::insert; using Tree::query; using Tree::rt;
struct SAM{
int Lst[MAXN] , Sst[MAXN] , fa[MAXN] , trans[MAXN][26] , endpos[MAXN];
int cnt = 1 , lst = 1 , L;
void insert(int len , int x){
int t = ++cnt , p = lst;
Lst[lst = t] = endpos[t] = len;
while(p && !trans[p][x]){
trans[p][x] = t;
p = fa[p];
}
if(!p) {Sst[t] = fa[t] = 1; return;}
int q = trans[p][x];
Sst[t] = Lst[p] + 2;
if(Lst[q] == Lst[p] + 1) {fa[t] = q; return;}
int k = ++cnt;
memcpy(trans[k] , trans[q] , sizeof(trans[k]));
Lst[k] = Lst[p] + 1; Sst[k] = Sst[q];
Sst[q] = Lst[p] + 2;
fa[k] = fa[q]; fa[q] = fa[t] = k;
while(trans[p][x] == q){
trans[p][x] = k;
p = fa[p];
}
}
}S , T;
char s[MAXN];
int mrk[MAXN] , sz[MAXN] , dfn[MAXN] , ts , LS;
vector < int > ch[MAXN];
void clear(){
memset(T.trans , 0 , sizeof(int) * 26 * (T.cnt + 1));
memset(T.fa , 0 , sizeof(int) * (T.cnt + 1));
memset(T.Lst , 0 , sizeof(int) * (T.cnt + 1));
memset(T.Sst , 0 , sizeof(int) * (T.cnt + 1));
memset(mrk , 0 , sizeof(int) * (T.cnt + 1));
T.lst = T.cnt = 1;
}
void dfs(int x){
dfn[x] = ++ts;
sz[x] = 1;
rt[ts] = rt[ts - 1];
if(S.endpos[x])
rt[ts] = insert(rt[ts] , 1 , LS , S.endpos[x]);
for(int i = 0 ; i < ch[x].size() ; ++i){
dfs(ch[x][i]);
sz[x] += sz[ch[x][i]];
}
}
void init(){
scanf("%s" , s + 1);
LS = strlen(s + 1);
for(int i = 1 ; i <= LS ; ++i)
S.insert(i , s[i] - 'a');
for(int i = 2 ; i <= S.cnt ; ++i)
ch[S.fa[i]].push_back(i);
dfs(1);
}
queue < int > q;
int in[MAXN];
long long ans;
void getans(){
for(int i = 2 ; i <= T.cnt ; ++i)
++in[T.fa[i]];
for(int i = 2 ; i <= T.cnt ; ++i)
if(!in[i]) q.push(i);
while(!q.empty()){
int t = q.front(); q.pop();
if(t == 1) continue;
if(mrk[t]){
ans -= min(mrk[t] , T.Lst[t]) - T.Sst[t] + 1;
mrk[T.fa[t]] = mrk[t];
}
if(!--in[T.fa[t]])
q.push(T.fa[t]);
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in" , "r" , stdin);
//freopen("out" , "w" , stdout);
#endif
init();
int Q;
for(scanf("%d" , &Q) ; Q ; --Q){
clear();
int l , r;
scanf("%s %d %d" , s + 1 , &l , &r);
int L = strlen(s + 1) , u = 1 , len = 0 , v = 1;
for(int i = 1 ; i <= L ; ++i)
T.insert(i , s[i] - 'a');
for(int i = 1 ; i <= L ; ++i){
while(u - 1 && !S.trans[u][s[i] - 'a'])
len = S.Lst[u = S.fa[u]];
if(S.trans[u][s[i] - 'a']){
u = S.trans[u][s[i] - 'a'];
++len;
}
while(u != 1 && !query(rt[dfn[u] + sz[u] - 1] , rt[dfn[u] - 1] , 1 , LS , l + len - 1 , r)){
--len;
if(len < S.Sst[u])
u = S.fa[u];
}
v = T.trans[v][s[i] - 'a'];
while(v - 1 && T.Sst[v] > len) v = T.fa[v];
mrk[v] = max(mrk[v] , len);
}
ans = 0;
getans();
for(int i = 2 ; i <= T.cnt ; ++i)
ans += T.Lst[i] - T.Sst[i] + 1;
printf("%lld\n" , ans);
}
return 0;
}
Luogu4770 NOI2018 你的名字 SAM、主席树的更多相关文章
- luogu4770 [NOI2018]你的名字 (SAM+主席树)
对S建SAM,拿着T在上面跑 跑的时候不仅无法转移要跳parent,转移过去不在范围内也要跳parent(注意因为范围和长度有关,跳的时候应该把长度一点一点地缩) 这样就能得到对于T的每个前缀,它最长 ...
- [NOI2018]你的名字(SAM+线段树合并)
考虑l=1,r=n的68分,对S和T建SAM,对T的SAM上的每个节点,计算它能给答案带来多少贡献. T上节点x代表的本质不同的子串数为mx[x]-mx[fa[x]],然后需要去掉所代表子串与S的最长 ...
- NOI2018 你的名字——SAM+线段树合并
题目链接在这里洛谷/LOJ 题目大意 有一个串\(S\),每次询问给你一个串\(T\),两个数\(L\)和\(R\),问你\(T\)有多少个本质不同的子串不是\(S[L,R]\)的子串 SOLUTIO ...
- 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)
[BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...
- 【NOI2019模拟2019.6.29】字符串(SA|SAM+主席树)
Description: 1<=n<=5e4 题解: 考虑\(f\)这个东西应该是怎样算的? 不妨建出SA,然后按height从大到小启发式合并,显然只有相邻的才可能成为最优答案.这样的只 ...
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...
- Luogu4770 NOI2018你的名字(后缀自动机+线段树合并)
先考虑l=1,r=n,并且不要求本质不同的情况.对原串建SAM,将询问串在上面跑,得到每个前缀的最长匹配后缀即可得到答案. 然后考虑本质不同.对询问串也建SAM,统计每个节点的贡献,得到该点right ...
- Luogu4770 NOI2018你的名字(后缀数组+线段树)
即求b串有多少个本质不同的非空子串,在a串的给定区间内未出现.即使已经8102年并且马上就9102年了,还是要高举SA伟大旗帜不动摇. 考虑离线,将所有询问串及一开始给的串加分隔符连起来,求出SA.对 ...
- 【NOI2018】你的名字(SAM & 线段树合并)
Description Hint Solution 不妨先讨论一下无区间限制的做法. 首先"子串"可以理解为"前缀的后缀",因此我们定义一个 \(\lim(i) ...
随机推荐
- 【读书笔记】iOS-应用内购买
Store Kit框架是一个应用内支付引擎.通过这个框架,付费应用可以实现用户付费购买内容的功能(比如为了获取额外的内容) 如果你发现Store Kit框架很难用,而且应用内付款不需要服务器端的支持, ...
- Html/Css 初步认识笔记
1.什么是 HTML ? HTML(HyperText Markup Language) 的学名是超文本标记语言. 标记用来表示网页内容要如何显示,自身不显示 .<我就是标记> 标记成对出 ...
- 小程序 青少儿书画 利用engineercms作为服务端
因为很多妈咪们喜欢发布自己宝宝的作品,享受哪些美好时刻,记录亲子创作过程. 为了方便妈咪们展示亲子创作,比如宝宝们画作,涂鸦,书法,作文,其他才艺,特利用engineercms作为服务端,重新设计了一 ...
- Java集合之TreeMap源码分析
一.概述 TreeMap是基于红黑树实现的.由于TreeMap实现了java.util.sortMap接口,集合中的映射关系是具有一定顺序的,该映射根据其键的自然顺序进行排序或者根据创建映射时提供的C ...
- springboot 升级到2.0后 context-path 配置 不起作用,不生效 不管用 皆是因为版本改动导致的在这里记录一下
不知不觉,新的项目已经将springboot升级为2.0版本了.刚开始没有配置server.contextpath,默认的“/”,然后今天放到自己的服务器上,所以就要规范名称. 结果,失败了,无论我 ...
- 手把手教你撸一个简易的 webpack
背景 随着前端复杂度的不断提升,诞生出很多打包工具,比如最先的grunt,gulp.到后来的webpack和Parcel.但是目前很多脚手架工具,比如vue-cli已经帮我们集成了一些构建工具的使用. ...
- 利用trie树实现前缀输入提示及trie的python实现
代码来自https://github.com/wklken/suggestion/blob/master/easymap/suggest.py 还实现了缓存功能,搜索某个前缀超过一定次数时,进行缓存, ...
- Django之form总结
复习Django项目结构: 主要的文件:manage.py,url.py,views.py,settings.py,models.py manage.py:项目管理文件,一般不做修改. url.py: ...
- 洗礼灵魂,修炼python(56)--爬虫篇—知识补充—编码之url编码
其实在最前面的某一篇博文里,是绝对提过编码的,有ASCII,有UTF-8,有GB2312等等,这些我绝对说过的. url编码 首先,Http协议中参数的传输是"key=value" ...
- Python零基础学习系列之三--Python编辑器选择
上一篇文章记录了怎么安装Python环境,同时也成功的在电脑上安装好了Python环境,可以正式开始自己的编程之旅了.但是现在又有头疼的事情,该用什么来写Python程序呢,该用什么来执行Python ...