[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\)结尾的串最长 ...
随机推荐
- Java动态代理实现及实际应用
一.代理的概念 动态代理技术是整个java技术中最重要的一个技术,它是学习java框架的基础,不会动态代理技术,那么在学习Spring这些框架时是只知应用不懂实现. 动态代理技术就是用来产生一个对象的 ...
- c do{}while(0)
1 goto bool foo(){ int *p = (int*)malloc(5*sizeof(int)); bool bOk = true;//执行并处理错误 if(!fun1()) goto ...
- 2019秋招Java面经(未完待续)
2019秋招Java面经(凭记忆回忆, 可能不准) 随着我们从大三升到大四...秋招也开始了. 秋招进行的还比较顺利, 刚开始没几天, 我的秋招就结束了. 到现在我玩了差不多十多天了, 总想着总结一下 ...
- js/vue图片压缩
js版 新建compressImage.js,内容如下: // 将base64转换为blob(有需要可加上,没需要可不加) function convertBase64UrlToBlob(urlDat ...
- mysql 设置skip_name_resolve参数 日志 [Warning] 'user' entry 'root@localhost' ignored in --skip-name-resolve mode
[环境介绍] 系统环境:Red Hat Enterprise Linux 7 + 5.7.25-enterprise-commercial-advanced-log MySQL Enterprise ...
- FCN网络
https://www.cnblogs.com/gujianhan/p/6030639.html
- sqli注入--利用information_schema配合双查询报错注入
目录 sqli-labs 5.6双查询报错注入通关 0x01 获取目标库名 0x02 获取库中表的数量 0x03 获取库中表名 0x04 获取目标表中的列数 0x05 获取目标表的列名 0x06 从列 ...
- PHP笔记:单引号与双引号区别
PHP笔记:单引号与双引号区别 php中使用字符串时,可以使用单引号或者双引号,这里总结一下二者的不同: 一.解析字符内容 双引号中的变量会会经过编译器解析 单引号中的变量不会被解析 如下: < ...
- python2 线程基础
1,感谢菜鸟教程, 线程基础:导入,创建函数,创建线和运行 import thread import time # 为线程定义一个函数 def print_time(threadName, delay ...
- adb devices 找不到设备
问题如图: 解决方法: 1.在开发人员选项中,找到USB调试,打开USB调试 2.如果还不行,下载360手机助手,连接手机,会自动安装驱动 3.再次adb devices,OK