loj#2720. 「NOI2018」你的名字

时间:2021-06-16 03:06:17

链接大合集:

loj

uoj

luogu

bzoj

单纯地纪念一下写的第一份5K代码。。。/躺尸

因为ZJOI都不会所以只好写NOI的题了。。。

总之字符串题肯定一上来就拼个大字符串跑后缀数组啦!

(为了便于说明,放一下样例的sa)

 #sbape#sgepe
#sgepe
#smape#sbape#sgepe
amgepe#smape#sbape#sgepe
ape#sbape#sgepe
ape#sgepe
bamgepe#smape#sbape#sgepe
bape#sgepe
cbamgepe#smape#sbape#sgepe
e
e#sbape#sgepe
e#sgepe
e#smape#sbape#sgepe
epe
epe#smape#sbape#sgepe
gepe
gepe#smape#sbape#sgepe
mape#sbape#sgepe
mgepe#smape#sbape#sgepe
pe
pe#sbape#sgepe
pe#sgepe
pe#smape#sbape#sgepe
sbape#sgepe
scbamgepe#smape#sbape#sgepe
sgepe
smape#sbape#sgepe

首先,对于每个T,考虑求出其子串总数。对于T每个后缀[i...|T|],用st表求该后缀和它前面第一个T的后缀的lcp,则这个后缀的有效子串即为[i...j](j属于[i+lcp,|T|])。

(对于"sgepe"的后缀"epe",lcp(10,14)=1,因此,其有效子串为"ep","epe")

然后,我们再考虑去掉其有效子串中S[l...r]中的部分。

考虑64pts的部分分,即l=1,r=n。对于T的后缀[i...|T|],只要求出L=max{lcp( T[i...|T|] , S[j...|S|] )},我们就能算出每个后缀对答案的贡献。可以发现,这两个部分分别在区间上取min和max,即分别递增、递减,因此我们只要找到两个函数值最靠近的位置即可。

考虑100pts,我们可以离线做。按询问的l从大到小排序,每次在线段树上加入新的字符串使得线段树中的字符串为S[i...|S|](i>=l)。显然,i>r的字符串并不会对答案产生影响,于是我们就可以愉快地线段树上二分啦!(二分需要找到最近的满足递减函数小于递增函数的位置,在线段树上需要先上去再下来,总之写起来很恶心就对了。。。写了个人不人鬼不鬼的zkw线段树(好吧其实我并不怎么会zkw线段树。。。)总之这就是长达5k的原因。。。)

(啊,代码真丑。)

(不知道是不是因为大家都是sam而我是sa所以这么慢。。。)

(sa:所以自己常数大反而怪我咯?←_←)

 #include <bits/stdc++.h>
using namespace std; #define rep(i,l,r) for(int i=l;i<=r;++i)
#define per(i,r,l) for(int i=r;i>=l;--i)
#define pb push_back
#define gc getchar()
#define pc putchar typedef pair<int,int> pii;
typedef long long ll; const int N=2e6+; int rd(){
int x=;char c=gc;
while(c<''||c>'') c=gc;
while(c>=''&&c<=''){
x=x*+c-'';
c=gc;
} return x;
} void prt(ll x){
if(x>) prt(x/);
pc(x%+'');
} //Q 查询字符串 l-r/r-l
int L,n,m,sa[N];
char s[N]; namespace Sa{
const int V=;
int ht[N],ar[][N],*rk,*tp,ct[N],mx,ln,st[V+][N],lg[N]; void get_sa(){
rk=ar[];tp=ar[]; rep(i,,n) ct[s[i]]++;
rep(i,,) ct[i]+=ct[i-];
rep(i,,n) sa[ct[s[i]]--]=i;
rep(i,,n){
if(s[sa[i]]!=s[sa[i-]]) ++mx;
rk[sa[i]]=mx;
} for(int k=;mx<n;k<<=){
ln=;
rep(i,n-k+,n) tp[++ln]=i;
rep(i,,n) if(sa[i]>k) tp[++ln]=sa[i]-k; rep(i,,mx) ct[i]=;
rep(i,,n) ct[rk[i]]++;
rep(i,,mx) ct[i]+=ct[i-];
per(i,n,) sa[ct[rk[tp[i]]]--]=tp[i]; swap(tp,rk);
mx=;
rep(i,,n){
int I=sa[i],J=sa[i-];
if(tp[I]!=tp[J]||tp[min(I+k,n+)]!=tp[min(J+k,n+)]) ++mx;
rk[I]=mx;
}
}
} void get_ht(){
rep(i,,n) lg[i]=lg[i>>]+; rep(i,,n){//ht[rk[i]]
if(rk[i]==) continue;
int v=ht[rk[i-]];if(v>) v--;
int J=sa[rk[i]-];
while(s[i+v]==s[J+v]) v++;
ht[rk[i]]=v;
} rep(i,,n) st[][i]=ht[i];
rep(j,,V) rep(i,,n)
st[j][i]=min(st[j-][i],st[j-][min(i+(<<(j-)),n)]);
} void init(){
get_sa();
get_ht();
} int Q(int l,int r){
if(l>r) swap(l,r);
if(l==r) return L+;
if(r>n) return ;
l++;
int j=lg[r-l+];
return min(st[j][l],st[j][r-(<<j)+]);
}
}
using Sa::Q;
using Sa::rk; namespace Tr{
const int M=(<<)-,H=(<<)-;
int mi[M+],zuo[M+],you[M+]; void build(){
rep(i,H+,M){
zuo[i]=you[i]=i-H;
mi[i]=L+;
}
per(i,H,){
zuo[i]=zuo[i<<];
you[i]=you[i<<|];
mi[i]=L+;
}
} void cg(int x,int val){
x+=H;mi[x]=val;
while(x!=){
x>>=;
mi[x]=min(mi[x],val);
}
} int rt,ctr; bool ck(int miv,int pos){
return (rt+)<Q(pos,ctr)+miv;
//即 (rt-miv+1)<Q(pos,ctr)
//即 递减函数小于递增函数
} int calc(int x){
if(x==ctr) return ;
return min(max(rt-mi[x+H]+,),Q(ctr,x));
} int get_mi(int x,int oi){//oi=0 oi=1
while((x<<)<M){
if(mi[x<<|oi]<=rt) x=(x<<)|oi;
else x=(x<<)|(oi^);
}
return x;
} int Qr(int R,int C){//修改rt ctr
rt=R;ctr=C; int ans=,x,miv,pos; x=ctr+H;miv=L+;
while(x!=){
x>>=;
if(x&) continue;
if(ck(min(miv,mi[x|]),you[x|])) miv=min(mi[x|],miv);
else{
x|=;
break;
}
}
while((x<<)<M){
int ls=(x<<);
if(ck(min(miv,mi[ls]),you[ls])){
miv=min(miv,mi[ls]);
x=(x<<|);
}
else x=ls;
}
pos=x-H; ans=max(calc(pos),ans);
//往左的第一个 mi[pos]<=rt
while(x!=){
if(x&){
if(mi[x^]<=rt){
x=x^;
break;
}
}
x>>=;
}
pos=get_mi(x,)-H;
ans=max(calc(pos),ans); x=ctr+H;miv=L+;
while(x!=){
x>>=;
if(x&){
if(ck(min(miv,mi[x^]),zuo[x^])) miv=min(miv,mi[x^]);
else{
x^=;
break;
}
}
}
while((x<<)<M){
int rs=(x<<|);
if(ck(min(miv,mi[rs]),zuo[rs])){
miv=min(miv,mi[rs]);
x=(x<<);
}
else x=rs;
}
pos=x-H; ans=max(calc(pos),ans);
//往右第一个
while(x!=){
if((x&)==){
if(mi[x|]<=rt){
x|=;
break;
}
}
x>>=;
}
pos=get_mi(x,)-H; ans=max(calc(pos),ans); return ans;
}
}
using Tr::cg;
using Tr::Qr; struct Query{
int l,r,no;
}a[N];
bool cmp(Query aa,Query bb){
return aa.l>bb.l;
}
vector<int> ps[N];
int hd[N],no[N],bg[N]; typedef long long ll;
ll ans[N]; int main(){
scanf("%s",s+);
L=n=strlen(s+); m=rd();
rep(i,,m){
s[++n]='#';
scanf("%s",s++n);
bg[i]=n;
n+=strlen(s++n); rep(j,bg[i]+,n) hd[j]=i,no[j]=n-j+;
a[i]=(Query){rd(),rd(),i};
} Sa::init(); rep(i,,n){
rep(j,sa[i],n) cerr<<s[j];
cerr<<endl;
} rep(i,,n) ps[hd[sa[i]]].pb(i); sort(a+,a++m,cmp);
Tr::build();
int R=L;
rep(i,,m){
while(a[i].l<=R) cg(rk[R],R) ,R--;
rep(j,,ps[a[i].no].size()-){
int lcp=,pos=ps[a[i].no][j];
if(j) lcp=Q(pos,ps[a[i].no][j-]);
lcp=max(lcp,Qr(a[i].r,pos)); if(no[sa[pos]]>lcp) ans[a[i].no]+=no[sa[pos]]-lcp;
}
} rep(i,,m) printf("%lld\n",ans[i]);
return ;
}
//得到sa ht st存ht
//vector存每个询问的位置 //l从大到小对询问排序
//处理T的重复子串
//线段树加点 线段树上二分最小值

loj#2720. 「NOI2018」你的名字的更多相关文章

  1. LOJ 2720 「NOI2018」你的名字——后缀自动机

    题目:https://loj.ac/problem/2720 自己总是分不清 “SAM上一个点的 len[ ] ” 和 “一个串的前缀在 SAM 上匹配的 len ”. 于是原本想的 68 分做法是, ...

  2. 【LOJ】&num;2720&period; 「NOI2018」你的名字

    题解 把S串建一个后缀自动机 用一个可持久化权值线段树维护每个节点的right集合是哪些节点 求本质不同的子串我们就是要求T串中以每个点为结束点的串有多少在\(S[l..r]\)中出现过 首先我们需要 ...

  3. LOJ&lowbar;&num;2720&period; 「NOI2018」你的名字 &lowbar;后缀数组&plus;主席树&plus;倍增

    题面: https://loj.ac/problem/2720 考虑枚举T串的每个后缀i,我们要做两件事. 一.统计有多少子串[i,j]在S中要求位置出现. 二.去重. 第二步好做,相当于在后缀数组上 ...

  4. 「NOI2018」你的名字

    「NOI2018」你的名字 题目描述 小A 被选为了\(ION2018\) 的出题人,他精心准备了一道质量十分高的题目,且已经 把除了题目命名以外的工作都做好了. 由于\(ION\) 已经举办了很多届 ...

  5. LOJ &num;2721&period; 「NOI2018」屠龙勇士(set &plus; exgcd)

    题意 LOJ #2721. 「NOI2018」屠龙勇士 题解 首先假设每条龙都可以打死,每次拿到的剑攻击力为 \(ATK\) . 这个需要支持每次插入一个数,查找比一个 \(\le\) 数最大的数(或 ...

  6. loj&num;2718&period; 「NOI2018」归程

    题目链接 loj#2718. 「NOI2018」归程 题解 按照高度做克鲁斯卡尔重构树 那么对于询问倍增找到当前点能到达的高度最小可行点,该点的子树就是能到达的联通快,维护子树中到1节点的最短距离 s ...

  7. loj&num;2721&period; 「NOI2018」屠龙勇士

    题目链接 loj#2721. 「NOI2018」屠龙勇士 题解 首先可以列出线性方程组 方程组转化为在模p意义下的同余方程 因为不保证pp 互素,考虑扩展中国剩余定理合并 方程组是带系数的,我们要做的 ...

  8. Loj &num;2719&period; 「NOI2018」冒泡排序

    Loj #2719. 「NOI2018」冒泡排序 题目描述 最近,小 S 对冒泡排序产生了浓厚的兴趣.为了问题简单,小 S 只研究对 *\(1\) 到 \(n\) 的排列*的冒泡排序. 下面是对冒泡排 ...

  9. loj 2719 「NOI2018」冒泡排序 - 组合数学

    题目传送门 传送门 题目大意 (相信大家都知道) 显然要考虑一个排列$p$合法的充要条件. 考虑这样一个构造$p$的过程.设排列$p^{-1}_{i}$满足$p_{p^{-1}_i} = i$. 初始 ...

随机推荐

  1. WordPress实现长篇文章&sol;日志&sol;单页面分页功能效果

    在WordPress里写文章,如果内容很多,你可能想要把文章分成几页来让访客浏览,这样既保持了网页的美观,也提高了网页的打开速度.但是在WordPress默认提供的按钮里,你可能找不到文章分页功能所对 ...

  2. JSP的九个隐式(内置)对象

    1.out 转译后对应JspWriter对象,其内部关联一个PrintWriter对象.是向客户端输出内容常用的对象. 2.request 转译后对应HttpServletRequest对象.客户端的 ...

  3. &dollar;&lpar;document&rpar;&period;Ready&lpar;&rpar;方法 VS OnLoad事件 VS &dollar;&lpar;window&rpar;&period;load&lpar;&rpar;方法

    $(document).Ready()方法 VS OnLoad事件 VS $(window).load()方法接触JQuery一般最先学到的是何时启动事件.在曾经很长一段时间里,在页面载入后引发的事件 ...

  4. Redis 常用命令总结

    连接操作相关的命令 quit:关闭连接(connection) auth:简单密码认证 持久化 save:将数据同步保存到磁盘 bgsave:将数据异步保存到磁盘 lastsave:返回上次成功将数据 ...

  5. CSS学习笔记-01-2D转换模块

    首先,mark 一下  css3 新增 的 2D 转换之 W3school 的链接: http://www.w3school.com.cn/css3/css3_2dtransform.asp 转换是使 ...

  6. 2&period;3《想成为黑客,不知道这些命令行可不行》&lpar;Learn Enough Command Line to Be Dangerous&rpar;——重命名,复制,删除

    最常用的文件操作除了将文件列出来外,就应该是重命名,复制,删除了.正如将文件列出来一样,大多数现代操作系统为这些任务提供了用户图形界面,但是在许多场景中,用命令行还是会更方便. 使用mv命令重命名一个 ...

  7. 【stanford C&plus;&plus;】容器III——Vector类

    主要介绍如下5个容器类——Vector, Stack,Queue,Map和Set,各个都表示一重要的抽象数据类型.另外,各个类都是一些简单类型的值的集合,所以称它们为容器类. 暂且我们先不需要知道它们 ...

  8. ubuntu 设置计划任务

    非常简便: crontab -e 以下是我的执行过程,输入命令后,会让我选择一个编辑器,我选的是2,因为后边写着easiest,最简单的. liuyx@myubuntu:/$ crontab -e n ...

  9. Android基础总结(三)SQLite,ListView,对话框

    测试 黑盒测试 测试逻辑业务 白盒测试 测试逻辑方法 根据测试粒度 方法测试:function test 单元测试:unit test 集成测试:integration test 系统测试:syste ...

  10. sql中的连接表达式,视图,事务等。

    给定两张表 表A ),description )); ,'N1','AD1'); ,'N2','AD2'); mysql> SELECT * FROM a; +----+------+----- ...