[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\)结尾的串最长 ...
随机推荐
- 将字符串存储到注册表中,长度一定是 strlen(text) + 1
参考:https://docs.microsoft.com/en-us/windows/desktop/sysinfo/registry-value-types 将字符串存储到注册表中,长度参数一定要 ...
- python第十三天,函数的嵌套定义,global,nonlocal关键字的使用,闭包及闭包的运算场景,装饰器
今日内容 1. 函数的嵌套定义 2.global,nonlocal关键字 3.闭包及闭包的运用场景 4.装饰器 函数的嵌套定义 1. 概念:在一个函数内部定义另一个函数 2 .为什么要有函数的嵌套定义 ...
- plsql 永久注册码适用个版本
注册码:Product Code:4t46t6vydkvsxekkvf3fjnpzy5wbuhphqzserial Number:601769 password:xs374ca
- 你不得不用的MAC软件开发工具软件,个个万里挑一
作为软件行业,尤其是程序员,Mac上都不得不安装一些必备的MAC软件开发工具软件,下面给大家分享一些必装的MAC软件开发工具软件,以备日后之需,有备无患. 其中,包含各种语言的主流 IDE.开发辅助. ...
- html css笔记zht
第3章 Img标签 路径问题 绝对路径:从盘符(C:\)出发的路径 (C:\Users\......) linux(绝对路径以 / 开头) 相对路径:( ./ 当前文件所在的目录)( ../上一级目录 ...
- I2C(三) linux3.4(内核分析)
目录 I2C(三) linux3.4(内核分析) (一)总线流程 bus.probe match i2c_device_probe (二)client注册 方式(一)静态加载 方式(二)指定设备 方式 ...
- Java裸写爬虫技术,运用多线程技术,高效爬取某个医疗机构网站数据
最近喜欢上了数据的庞大的感觉,就爬取了一下某个医疗机构网站医疗数据,由于数据量庞大,只爬取了江西省的各个市的各个医院的各个科室的各个科室.中各种信息.其中用的持久层技术是hibernate框架,和用到 ...
- 对于iOS架构的认识过程
MVC 经典就是经典,没有之一.iOS中MVC架构,看懂斯坦福大学白胡子老头这张图基本上就可以了. 斯坦福大学MVC架构.png 简单理解,就是Controller对象拥有View和Model对象 ...
- Jupyter Notebook中的快捷键
1.快捷键 Jupyter Notebook 有两种键盘输入模式.编辑模式,允许你往单元中键入代码或文本:这时的单元框线是绿色的.命令模式,键盘输入运行程序命令:这时的单元框线是灰色. 命令模式 (按 ...
- uwsgi+anaconda+nginx部署django项目(ubuntu下)
conda 环境不必多说: conda(或source) activate test 进入test虚拟环境 接下来安装uwsgi: pip install uwsgi 在conda环境下大概率安装 ...