[NOI2018]你的名字
题解:
前68分非常简单
建立SAM
另一个串在上面跑,然后求一个树链的并
我们会发现暴力就是min(l^2,n)的
所以复杂度最多是nsqrt(n)的
当然我们也可以nlogn维护
把所有点按照dfs序排序,每个点到根的距离减去排序后
相邻两点LCA到根的距离就是树链的并的长度
不过要注意一下由于有权值
所以每个点在当前点可能只取了一部分权值
调试的时候发现 cnt没清空
***
windows下不开O2不会跑到其他数组里,开了O2就会跑到其他数组里
linux下开不开都会跑过去
linux下调试不能用register,不然条件断点就炸了
暴力map注意s[i]-'a'要+1 不然就是0与没有这一位等价了
代码:
#include <bits/stdc++.h> #define IL inline #define ll long long #define rint register int #define rep(i,h,t) for (int i=h;i<=t;i++) #define dep(i,t,h) for (int i=t;i>=h;i--) using namespace std; const int N=4e6; char s[N]; int cnt3,m,cnt,ve[N]; ll ans,ans2; int f[N]; struct hz{ int lst,node,fa[N],size[N],len[N],ch[N][]; hz() { lst=; node=; } IL void extend(int c) { int f=lst,p=++node; lst=p; len[p]=len[f]+; size[p]=; while (f&&!ch[f][c]) ch[f][c]=p,f=fa[f]; if (!f) { fa[p]=; return;}; int x=ch[f][c],y=++node; if (len[f]+==len[x]) {fa[p]=x; node--;return;}; len[y]=len[f]+; fa[y]=fa[x]; fa[x]=fa[p]=y; memcpy(ch[y],ch[x],sizeof(ch[x])); while (f&&ch[f][c]==x) ch[f][c]=y,f=fa[f]; } IL void dfs(int x,int y) { /* if (f[x]>=y||!x) return; ve[++cnt]=x; if (!f[x]) ans-=y-len[fa[x]]; else ans-=y-f[x]; f[x]=y; dfs(fa[x],len[fa[x]]);*/ while (f[x]<y&&x) { ve[++cnt]=x; if (!f[x]) ans-=y-len[fa[x]]; else ans-=y-f[x]; f[x]=y; y=len[fa[x]]; x=fa[x]; } } }S1,S2; int main() {
freopen("1.in","r",stdin);
freopen("2.out","w",stdout);
// ios::sync_with_stdio(false); scanf("%s",s); int l=strlen(s); rep(i,,l) S1.extend(s[i-]-'a'); cin>>m; rep(i,,m) { int now=,x,y; ans=; scanf("%s%d%d",s,&x,&y); l=strlen(s); int cnt2=; rep(i,,l-) S2.extend(s[i]-'a'); rep(i,,S2.node) ans+=S2.len[i]-S2.len[S2.fa[i]]; for (int i=;i<l;i++) { int k=s[i]-'a'; while (!S1.ch[now][k]&&now) { now=S1.fa[now],cnt2=S1.len[now]; } if (!now) { now=; cnt2=; } else { cnt2++; now=S1.ch[now][k]; S1.dfs(now,min(S1.len[now],cnt2)); } } cout<<ans<<endl; rep(i,,cnt) f[ve[i]]=; memset(S2.fa,,sizeof(S2.fa[])*(S2.node+)); memset(S2.ch,,sizeof(S2.ch[])*(S2.node+)); S2.lst=S2.node=;
cnt=; } return ; }
#updata: 12.26
当初的这个暴力真是很厉害。。。
方法1:这题比较容易想到的是广义后缀自动机
依然先看前60分
我们可以建立广义后缀自动机,然后size1>0,size0=0的点被算入就可以
然后我们会发现后缀自动机的建立过程之前点的父亲可能变为当前点
所以对这么情况特判一下就好了
然后这种做法需要还原一大串东西
这个还原超级容易写错。。。我改了半天,要注意各种还原顺序
#include <bits/stdc++.h>
#define ll long long
#define rll register ll
#define rep(i,h,t) for (rll i=h;i<=t;i++)
#define dep(i,t,h) for (rll i=t;i>=h;i--)
#define me(x) memset(x,0,sizeof(x))
#define mep(x,y) memcpy(x,y,sizeof(y))
using namespace std;
const ll N=3e6;
char s[N];
ll t[N],a[N],len[N],ch[N][],ch1[N][];
ll lst=,node=,fa[N],fa1[N],len1[N];
bool tt[N],tt1[N];
struct re{
ll a,b,c;
};
stack<re> Qc,Qf;
void extend(ll c)
{
ll f=lst;
if (ch[f][c]&&len[ch[f][c]]==len[f]+)
{
lst=ch[f][c];
return;
}
ll p=++node; lst=p;
len[p]=len[f]+; //size[p][pl]=1;
while (f&&!ch[f][c])
Qc.push((re){f,c,}),
ch[f][c]=p,
f=fa[f];
if (!f) { fa[p]=; return;};
ll x=ch[f][c],y=++node;
if (len[f]+==len[x]) {fa[p]=x; node--;return;};
Qf.push((re){x,fa[x]});
len[y]=len[f]+;
fa[y]=fa[x];
fa[x]=fa[p]=y;
tt[y]=tt[x];
memcpy(ch[y],ch[x],sizeof(ch[x]));
while (f&&ch[f][c]==x)
{
Qc.push((re){f,c,x});
ch[f][c]=y,f=fa[f];
}
}
int main()
{
freopen("2.in","r",stdin);
freopen("1.out","w",stdout);
ios::sync_with_stdio(false);
cin>>s;
ll l=strlen(s);
rep(i,,l) extend(s[i-]-'a');
rep(i,,node) tt[i]=;
lst=;
ll k;
cin>>k;
ll node1=node,lst1=lst;
mep(fa1,fa); mep(ch1,ch);
rep(i,,k)
{
while (!Qf.empty()) Qf.pop();
while (!Qc.empty()) Qc.pop();
// Qf.clear(); Qc.clear();
node=node1; lst=lst1;
ll x,y;
cin>>s>>x>>y;
l=strlen(s);
rep(i,,l) extend(s[i-]-'a');
ll ans=;
rep(i,node1+,node) if (!tt[i]) ans+=len[i]-len[fa[i]];
cout<<ans<<endl;
while (!Qc.empty())
{
re x=Qc.top(); Qc.pop();
ch[x.a][x.b]=x.c;
}
while (!Qf.empty())
{
re x=Qf.top(); Qf.pop();
fa[x.a]=x.b;
}
rep(i,node1+,node)
{
fa[i]=len[i]=tt[i]=;
me(ch[i]);
}
// mep(ch,ch1);
// mep(fa,fa1);
}
return ;
}
然后这个好像做不了满分。。
因为改变了儿子之后要重新合并right集合
因为启发式是均摊的所以这个直接gg了
方法2:
广义后缀自动机在这里的缺点是改变了第一个后缀自动机的状态
所以我们考虑建两个后缀自动机,然后同时在第一个上面跑
先考虑前68分
那么跑到一个点时,我们需要知道第一个自动机的len
这说明第一个自动机上与到当前节点的后缀最长匹配
但不能直接用这个len,而是$min(len,lstans+1)$
原因是后缀自动机的$len[ch[x][c]$]可能不是$len[x]+1$
比如$bababc$这个串
原先是$bab$,它的$len$是$3$,现在变成$babc$,它的$len$就变成了$7$
最后答案就等于$$\sum\limits_{i=1}^{node2} {MAX(0,len[i]-MAX(lazy[i],len[fa[i]]))}$$
(注意更新当前点的时候同时更新一下复制点的信息,就是后缀自动机中$len[x]!=len[f]+1$而新增的点
这个点可以理解成把第二个SAM当前点的信息分到了两个点上)
然后考虑l,r任意
首先肯定是可持久化线段树合并处理第一个$SAM$的$right$集合
然后查询的时候,我们很明显不能根据当前点$ch[x][c]$为不为空来判断
而是需要判断$ch[x][c]$的right集合在不在区间里面
既然是右端点,我们肯定要找在r左边的右端点的最大值
然后刚开始就这么写了发现有bug
后来想了一下,如果$right$的在里面,但是$right-len+1$在l左边
但是它的儿子的$right right-len+1 $可能是更优的
很容易想到我们只需要判断$right-fa[len]+1$在不在l右边就行了
为什么呢
因为如果它在右边,说明它比儿子优,如果不在,说明儿子可能比它优但不会劣于它(因为儿子还可以取这个点)
然后这个还是挺好写的
#include <bits/stdc++.h>
using namespace std;
#define rint register int
#define IL inline
#define rep(i,h,t) for (int i=h;i<=t;i++)
#define dep(i,t,h) for (int i=t;i>=h;i--)
#define me(x) memset(x,0,sizeof(x))
#define ll long long
#define mid ((h+t)>>1)
#define mep(x,y) memcpy(x,y,sizeof(y))
namespace IO
{
char ss[<<],*A=ss,*B=ss;
IL char gc()
{
return A==B&&(B=(A=ss)+fread(ss,,<<,stdin),A==B)?EOF:*A++;
}
template<class T>void read(T &x)
{
rint f=,c;while (c=gc(),c<||c>) if (c=='-') f=-; x=(c^);
while (c=gc(),c>&&c<) x=(x<<)+(x<<)+(c^); x*=f;
}
char sr[<<],z[]; int C=-,Z;
template<class T>void wer(T x)
{
if (x<) sr[++C]='-',x=-x;
while (z[++Z]=x%+,x/=);
while (sr[++C]=z[Z],--Z);
}
IL void wer1() {sr[++C]=' ';}
IL void wer2() {sr[++C]='\n';}
template<class T>IL void maxa(T &x,T y) { if (x<y) x=y;}
template<class T>IL void mina(T &x,T y) { if (x>y) x=y;}
template<class T>IL T MAX(T x,T y){ return x>y?x:y;}
template<class T>IL T MIN(T x,T y){ return x<y?x:y;}
}
using namespace IO;
const int INF=1e9;
const int N=1.5e6;
char s[N];
struct hz{
int lst,node,fa[N],size[N],len[N],ch[N][],jl[N];
hz() {node=lst=;}
void extend(int c)
{
int f=lst;
if (ch[f][c]&&len[ch[f][c]]==len[f]+)
{
lst=ch[f][c];
return;
}
int p=++node; lst=p; size[p]=;
len[p]=len[f]+;
while (f&&!ch[f][c]) ch[f][c]=p,f=fa[f];
if (!f) { fa[p]=; return;};
int x=ch[f][c],y=++node;
if (len[f]+==len[x]) {fa[p]=x; node--;return;};
len[y]=len[f]+; fa[y]=fa[x]; fa[x]=fa[p]=y;
memcpy(ch[y],ch[x],sizeof(ch[x]));
while (f&&ch[f][c]==x) ch[f][c]=y,f=fa[f];
}
}T1,T2;
int head[N],n,pos[N],l,rt[N];
struct re{
int a,b;
}e[N*];
IL void arr(int x,int y)
{
e[++l].a=head[x];
e[l].b=y;
head[x]=l;
}
struct sgt{
int v1[N*],ls[N*],rs[N*],num;
sgt()
{
rep(i,,N*-) v1[i]=;
}
IL void updata(int x)
{
v1[x]=MAX(v1[ls[x]],v1[rs[x]]);
}
int merge(int x,int y)
{
if (!x||!y) return x^y;
int now=++num;
ls[now]=merge(ls[x],ls[y]);
rs[now]=merge(rs[x],rs[y]);
updata(now);
return now;
}
int query2(int x,int h,int t,int h1,int t1)
{
if (h1<=h&&t<=t1) return v1[x];
int ans=;
if (h1<=mid) ans=query2(ls[x],h,mid,h1,t1);
if (mid<t1) maxa(ans,query2(rs[x],mid+,t,h1,t1));
return ans;
}
void insert(int &x,int h,int t,int pos)
{
if (!x) x=++num;
if (h==t)
{
v1[x]=pos; return;
}
if (pos<=mid) insert(ls[x],h,mid,pos);
else insert(rs[x],mid+,t,pos);
updata(x);
}
}S;
void dfs(int x)
{
for (rint u=head[x];u;u=e[u].a)
{
int v=e[u].b;
dfs(v);
rt[x]=S.merge(rt[x],rt[v]);
}
if (pos[x]) S.insert(rt[x],,n,pos[x]);
}
int x,y;
IL bool pd(int now)
{
int kk1=S.query2(rt[now],,n,x,y);
if (kk1-T1.len[T1.fa[now]]+<x) return ; else return ;
}
int main()
{
ios::sync_with_stdio(false);
cin>>s;
int l=strlen(s);
rep(i,,l)
{
T1.extend(s[i-]-'a');
pos[T1.lst]=pos[T1.node]=i;
}
n=T1.node;
// cerr<<T1.node<<endl;
rep(i,,n) arr(T1.fa[i],i);
dfs();
// cerr<<S.num<<endl;
int k; cin>>k;
rep(i,,k)
{
cin>>s; cin>>x>>y;
l=strlen(s);
int now=,cnt=;
rep(i,,l)
{
T2.extend(s[i-]-'a');
while (now&&(!T1.ch[now][s[i-]-'a']||pd(T1.ch[now][s[i-]-'a']))) now=T1.fa[now];
int kk1=S.query2(rt[now],,n,x,y);
cnt=MIN(cnt,MIN(kk1-x+,T1.len[now]));
if (now)
{
now=T1.ch[now][s[i-]-'a'];
int kk1=S.query2(rt[now],,n,x,y);
cnt=MIN(cnt+,MIN(kk1-x+,T1.len[now]));
T2.jl[T2.lst]=cnt;
} else now=,T2.jl[T2.lst]=,cnt=;
T2.jl[T2.node]=T2.jl[T2.lst];
}
ll ans=;
rep(i,,T2.node)
ans+=MAX(,T2.len[i]-MAX(T2.len[T2.fa[i]],T2.jl[i]));
cout<<ans<<endl;
rep(i,,T2.node) T2.size[i]=T2.fa[i]=T2.len[i]=T2.jl[i]=;
rep(i,,T2.node) me(T2.ch[i]); T2.node=T2.lst=;
}
return ;
}
[NOI2018]你的名字的更多相关文章
- BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并
题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...
- 【BZOJ5417】[NOI2018]你的名字(线段树,后缀自动机)
[BZOJ5417][NOI2018]你的名字(线段树,后缀自动机) 题面 BZOJ 洛谷 题解 首先考虑\(l=1,r=|S|\)的做法,对于每次询问的\(T\)串,暴力在\(S\)串的\(SAM\ ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- [NOI2018]你的名字(后缀自动机+线段树)
题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...
- [NOI2018]你的名字(后缀自动机+线段树合并)
看到题目名字去补番是种怎么样的体验 我只会 \(68\) 分,打了个暴力.正解看了一会儿,发现跟 \([HEOI2016/TJOI2016]\) 字符串很像,用线段树合并维护 \(endpos\) 集 ...
- [BZOJ5417] [NOI2018]你的名字
Description 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的, ...
- [NOI2018]你的名字(68pts) 后缀自动机
讲解在满分做法的博客中 Code: #include <cstdio> #include <algorithm> #include <cstring> #defin ...
- UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)
NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...
- #P4770 [NOI2018]你的名字 的题解
题目背景 实力强大的小A 被选为了ION2018 的出题人,现在他需要解决题目的命名问题. 题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外 ...
- 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]
传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...
随机推荐
- MySQ数据备份
MySQL备份概述 问题:备份和冗余有什么区别? 备份:能够防止由于机械故障以及人为操作带来的数据丢失,例如将数据库文件保存在了其它地方. 冗余:数据有多份冗余,但不等于备份,只能防止机械故障带来的数 ...
- CentOS 7 网卡配置对比
1.DHCP模式(原始) [root@centos7-minimal /]# vim /etc/sysconfig/network-scripts/ifcfg-eno16777736 TYPE=&qu ...
- I/O模型系列之五:IO多路复用 select、poll、epoll
IO多路复用之select.poll.epoll IO多路复用:通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. 应用:适用于针 ...
- 光刻技术的原理和EUV光刻技术前景
本文转载自微信公众号 半导体技术天地, 链接 https://mp.weixin.qq.com/s/EEBkSQ_Yc8RYFO18VpO8ow
- docker常用命令总结
1.docker ps 查看当前正在运行的容器 2.docker ps -a 查看所有容器的状态 3.docker start/stop id/name 启动/停止某个容器 4.docker ...
- Technocup 2019 - Elimination Round 2
http://codeforces.com/contest/1031 (如果感觉一道题对于自己是有难度的,不要后退,懂0%的时候敲一遍,边敲边想,懂30%的时候敲一遍,边敲边想,懂60%的时候敲一遍, ...
- O2O淘宝优惠券代码总结
一.数据集预处理 1.数据读入 import pandas as pd import numpy as np import datetime as date import datetime as dt ...
- selenium定位方式-Xpath使用方法
什么是Xpath? XPath是XML的路径语言,通俗一点讲就是通过元素的路径来查找到这个标签元素. 一. 在火狐浏览器上安装Xpath 方法如下: 1.使用 Firefox 访问 https://a ...
- python面试题一个字符串是否由重复的子字符串组成
一,给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成.给定的字符串只含有小写英文字母,并且长度不超过10000. 输入: "abab" 输出: True 解释: 可由 ...
- 【转】Java中的新生代、老年代、永久代和各种GC
JVM中的堆,一般分为三大部分:新生代.老年代.永久代: 1 新生代 主要是用来存放新生的对象.一般占据堆的1/3空间.由于频繁创建对象,所以新生代会频繁触发MinorGC进行垃圾回收. 新生代又分为 ...