首页 > 其他分享 >NOI2018 你的名字。

NOI2018 你的名字。

时间:2024-01-23 10:34:54浏览次数:22  
标签:par idx int text tr len 名字 NOI2018

您的名字。

做法好像和其他题解不太一样。

空间少个 \(\log\),而且常数小。

description

给定长度为 \(n\) 的字符串 \(S\)。

\(Q\) 次询问,第 \(t\) 次给定字符串 \(T_t\) 以及正整数 \(l,r\in [1,n]\)。

每次询问回答 \(T_t\) 有几个本质不同子串在 \(S_{l\dots r}\) 中没有出现过。

强制在线

  • \(n\leq 5\times 10^5\)

  • \(Q\leq 10^5\)

  • \(|T_t|\leq 5\times 10^5,\sum |T_t|\leq 10^6\)

solution

按照 \(r\) 不降离线。

对于第 \(t\) 个询问,先计算出来 \(w_t\) 表示字符串 \(T_t\) 的本质不同子串个数。

然后要容斥掉在 \(S_{l\dots r}\) 中出现过的。

通过在 SAM 上跳 parent 匹配出 \(T_t\) 的每个前缀 \(i\) 的后缀和 \(S\) 的最长匹配长度 \(Len_i\),并求出匹配到了 \(S\) 的 SAM 上的点 \(p_i\)。

再计算出 \(T_t\) 的每个前缀 \(i\) 有哪些长度的后缀是在 \(T_t\) 中第一次出现,容易发现,这些后缀的长度是连续的,构成区间 \([\text{minl}_i,\text{maxl}_i]\),这一步可以在 \(T_t\) 的 SAM 的 parent 树上 dfs 实现。

然后我们需要知道前缀 \(i\) 和 \(S_{l\dots r}\) 的最长匹配长度,和 \([\text{minl}_i,\text{maxl}_i]\) 取交就是对答案的贡献。

我们已经求出了前缀 \(i\) 匹配到的点 \(p\),那么能只有 \(p\) 和其 parent 树上的祖先可以和前缀 \(i\) 匹配。因为我们离线了,所以不需要考虑右端点的限制。设 \(S\) 的 SAM 上点 \(x\) 当前的 \(\text{endpos}\) 集合中最大的元素是 \(\text{maxr}(x)\),\(x\) 表示的字符串中最长的长度记作 \(\text{len}(x)\)。那么 \(x\) 代表的一些字符串和前缀 \(i\) 的最长匹配长度就是 \(\min(\text{maxr}(x)-l+1,\text{len}(x))\)。

容易发现,点 \(p\) 向上走的过程中,\(\text{len}\) 是递减,\(\text{maxr}\) 是不降的,所以可以二分 \(\text{maxr}-\text{len}+1\) 和 \(l\) 的大小,最长长度一定在临界处两个点取到。

具体实现可以用 LCT,每次离线的右指针右移就 access,然后在 Splay 上打标记,二分也可以在 Splay 上完成。注意,这时求出来的最长长度可能包含了 \(p\) 点原本不能匹配的字符串(即 \(Len_i<\text{len}(p)\),但是实际有可能取到比 \(Len_i\) 大),所以求出来的长度要和 \(Len_i\) 取 \(\min\)。

时间复杂度 \(O(n\log n+\sum |T|\log |T|)\),空间复杂度 \(O((n+\sum |T|)|\Sigma|)\)。

顺便提一下,这题原数据似乎有一部分比较水,Splay 上二分的方向写反得了 54 分 qwq

code

#include<bits/stdc++.h>

using namespace std;
using E=long long;
constexpr int inf=0x3f3f3f3f;

struct SAM{
  vector<array<int,26>> ch;
  vector<int> fa,len;
  int idx,lst;

  int extend(int c){
    idx++;
    len[idx]=len[lst]+1;
    int p=lst;
    while(p&&ch[p][c]==0){
      ch[p][c]=idx;
      p=fa[p];
    }
    if(!p) return fa[idx]=1,lst=idx,idx;
    int q=ch[p][c];
    if(len[q]==len[p]+1) return fa[idx]=q,lst=idx,idx;
    int np=idx;
    idx++;
    fa[idx]=fa[q],ch[idx]=ch[q],len[idx]=len[p]+1;
    while(p&&ch[p][c]==q){
      ch[p][c]=idx;
      p=fa[p];
    }
    return fa[q]=fa[np]=idx,lst=np,np;
  }

  SAM(int n){
    ch.resize(n*2+1);
    fa.resize(n*2+1),len.resize(n*2+1);
    lst=idx=1;
  }

};

struct linkCutTree{
  struct node{
    int son[2],par,tag,w,val;
    node(){
      son[0]=son[1]=par=tag=w=val=0;
    }
  };

  vector<node> tr;

  linkCutTree(int sz){
    tr=vector<node>(sz+1);
  }

  #define ls(u) tr[(u)].son[0]
  #define rs(u) tr[(u)].son[1]
  #define par(u) tr[(u)].par
  #define w(u) tr[(u)].w
  #define tag(u) tr[(u)].tag
  #define val(u) tr[(u)].val

  void spread(int u,int v){
    if(!u) return;
    w(u)=v-val(u)+1;
    tag(u)=v;
  }

  void dn(int u){
    if(tag(u)) spread(ls(u),tag(u)),spread(rs(u),tag(u)),tag(u)=0;
  }

  int get(int u){ return u==rs(par(u)); }
  int nroot(int u){ return u==ls(par(u))||get(u); }

  void rotate(int u){
    int p=par(u),j=par(p),op=get(u);
    if(nroot(p)) tr[j].son[get(p)]=u;
    if(tr[u].son[op^1]) par(tr[u].son[op^1])=p;
    tr[p].son[op]=tr[u].son[op^1];
    tr[u].son[op^1]=p;
    par(p)=u;
    par(u)=j;
  }

  void update(int u){
    if(nroot(u)) update(par(u));
    dn(u);
  }

  void splay(int u){
    update(u);
    while(nroot(u)){
      int f=par(u);
      if(nroot(f)){
        if(get(u)==get(f)) rotate(f);
        else rotate(u);
      }
      rotate(u);
    }
  }

  void access(int u){
    int p=0;
    while(u){
      splay(u);
      rs(u)=p;
      p=u;
      u=par(u);
    }
  }

  void reset(int u,int v,int fa){
    par(u)=fa,val(u)=v; w(u)=-v+1;
  }

  #undef ls
  #undef rs
  #undef tag
  #undef par
  #undef w
  #undef val
};

int main(){

#ifdef zzafanti
  freopen("in.in","r",stdin);
#endif // zzafanti

  cin.tie(nullptr),cout.tie(nullptr)->sync_with_stdio(false);

  string Str;
  cin>>Str;
  int n=Str.size();
  Str=" "+Str;

  SAM sam1(n);
  vector<int> pos(n+1);
  int Q;
  cin>>Q;
  vector<pair<int,int>> querys(Q+1);
  vector<E> ans(Q+1);
  vector<vector<pair<int,string>>> Tp(n+1);

  for(int t=1; t<=Q; t++){
    string T;
    cin>>T;
    int l,r;
    cin>>l>>r;
    Tp[r].emplace_back(make_pair(t,T));
    querys[t]=make_pair(l,r);
  }
  for(int i=1; i<=n; i++){
    pos[i]=sam1.extend(Str[i]-'a');
  }
  linkCutTree S(sam1.idx);
  for(int i=1; i<=sam1.idx; i++){
    S.reset(i,sam1.len[i],sam1.fa[i]);
  }

  for(int R=1; R<=n; R++){
    S.access(pos[R]);
    S.splay(1);
    S.spread(1,R);

    for(auto pt:Tp[R]){
      int l=querys[pt.first].first,r=querys[pt.first].second;
      string &T=pt.second;
      int t=pt.first;
      int m=T.size();
      SAM sam(m+1);
      vector<int> maxr(m*2+1);
      for(int i=1; i<=m; i++){
        maxr[sam.extend(T[i-1]-'a')]=i;
      }
      vector<vector<int>> ver(m*2+1);
      for(int i=2; i<=sam.idx; i++){
        ans[t]+=(sam.len[i]-sam.len[sam.fa[i]]);
        ver[sam.fa[i]].emplace_back(i);
      }
      vector<int> maxl(m+1,0),minl(m+1,inf);
      function<void(int)> dfs=[&](int u)->void{
        for(auto p:ver[u]){
          dfs(p);
          maxr[u]=max(maxr[u],maxr[p]);
        }
        maxl[maxr[u]]=max(maxl[maxr[u]],sam.len[u]);
        minl[maxr[u]]=min(minl[maxr[u]],sam.len[sam.fa[u]]+1);
        assert(minl[maxr[u]]>0);
      };
      dfs(1);

      int now=0,p=1;
      for(int i=1; i<=m; i++){
        int c=T[i-1]-'a';
        while(p!=1&&sam1.ch[p][c]==0) p=sam1.fa[p],now=sam1.len[p];
        if(sam1.ch[p][c]) p=sam1.ch[p][c],now++;

        int res=0;
        S.access(p);
        S.splay(p);
        //cerr<<S.tr[p].val<<endl;
        int tmp=p,lst=0,llst=0;
        while(tmp){
          llst=tmp;
          //cerr<<tmp<<' '<<S.tr[tmp].w<<' '<<l<<endl;
          if(S.tr[tmp].w<=l) lst=tmp,tmp=S.tr[tmp].son[0];
          else tmp=S.tr[tmp].son[1];
          if(tmp) S.dn(tmp);
        }
        tmp=lst;
        if(tmp){
          S.splay(tmp);
          //cerr<<S.tr[tmp].w<<' '<<S.tr[tmp].val<<endl;
          res=(S.tr[tmp].w+S.tr[tmp].val-1)-l+1;
          res=max(0,res);
          tmp=S.tr[tmp].son[0];
        }else res=S.tr[p].val,S.splay(llst);
        if(tmp){
          while(S.tr[tmp].son[1]) tmp=S.tr[tmp].son[1],S.dn(tmp);
          S.splay(tmp);
          //cerr<<tmp<<' '<<S.tr[tmp].val<<' '<<S.tr[tmp].w<<endl;
          res=max(res,S.tr[tmp].val);
        }
        res=min(res,now);
        ans[t]-=max(0,min(maxl[i],res)-minl[i]+1);
      }
    }
  }

  for(int i=1; i<=Q; i++) cout<<ans[i]<<'\n';

  return 0;
}

标签:par,idx,int,text,tr,len,名字,NOI2018
From: https://www.cnblogs.com/FreshOrange/p/17981771

相关文章

  • Boto3按名字搜索AWS Image并返回Image的相关参数 (Python)
    文章目录小结问题及解决参考小结本文记录使用Python脚本和Boto3按名字搜索AWSImage并返回AWSImage的相关参数。问题及解决记得操作之前拿到相应的权限:exportAWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxxxxxxxxxx"exportAWS_SECRET_ACCESS_KEY="yyyyyyyyyyyyyyyyyyyyyyyyyyyy"e......
  • P7811 [JRKSJ R2] 你的名字。
    \(\text{Links}\)P7811[JRKSJR2]你的名字。LuoguBlog题外话纪念一下300蓝紫最开始看到这题的时候没做出来,今天突然就会了,看来写大分块还是有点用的第一次写分块套ST(虽然不是第一次有这个想法,但以前只口胡过),还是写一下ARF我的神(乱入题意求区间\([l,r]......
  • P4786 [BalkanOI2018] Election 题解
    题意给定一个长度为\(n\)的字符串\(s\),有\(m\)个询问,每次询问最少需要删掉多少个字符才能使\(l\)到\(r\)组成的字符串当中的每一个前缀和后缀都满足C的数量不小于T的数量。思路因为要满足C的数量不小于T的数量,我们不妨设字符C的位置的值为\(1\),字符S的位......
  • C#如何对中文名字 按 姓氏 排序
    names.Sort((a,b)=>a.name.CompareTo(b.name)); usingSystem;usingSystem.Collections.Generic;usingSystem.Globalization;classProgram{staticvoidMain(){List<string>names=newList<string>{"张三",&qu......
  • 查找列表(表格的列名)是否包含某些列名字符串
    lis=["非关键词","关键词1","/s关键词2/s","重复的关键词1"]keywords=["关键词1","关键词2","关键词3"]result={}foriinkeywords:find=Falseforj,kinenumerate(lis):ifnotfind:......
  • 你的名字。
    第一章梦让人怀念的声音和气味,丰瑞的光线和温度。我(♀)和某个对我来说重要的人毫无间隙的附着在一起。难以分开的结合。宛如尚在乳房环抱中的婴儿一样,不安和寂寞没有一点踪影。所有的一切都还没失去,无比甘美的感情,涌动充盈在体内。突然,睁开眼睛。天花板。房间,清晨。一个人。......
  • 【分享代码片段】terraform中,如何从刚刚创建的 deployment 中获得所有容器的名字和 ip
    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢!cnblogs博客zhihuGithub公众号:一本正经的瞎扯不好意思,刚刚才开始用terraform,或许是更好的办法而我不知道。知道的朋友请一定教教我。下面是我的办法:provider"kubernetes"{config_path="../k8s.yaml"}......
  • P4119 [Ynoi2018] 未来日记
    \(\text{Links}\)LuoguBlogP4119[Ynoi2018]未来日记题外话个人生涯中第一道独立通过的Ynoi大分块!!同时也是个人生涯中通过的第十道Ynoi系列题目!!卡了好久结果加了个优化就过了/yunAC那一瞬间的场面好像56SecondsLater/ll感觉\(8.5\)的评分还是有点虚......
  • EndNote如何显示论文中国作者名字第二个字的拼音?
      本文介绍利用EndNote软件,对论文参考文献中英文文献的汉语拼音姓名(即含有中国作者的英文论文)的名的第二个字的首写字母加以补充显示。  例如,假如有如下一篇文章:  可知其第一作者的姓为Kong,名为Xiangbin,很显然,该作者除了姓之外的名共有两个字。假如,我们现在希望出现在参考......
  • 使用new关键字,是用来调用这个对象,并给了一个新名字和内存
    new关键字是用于创建对象的关键字。它会分配内存并初始化对象。当我们使用new关键字创建对象时,会自动调用该对象的构造方法。构造方法可以用于初始化类的属性,并为对象分配内存。例如,以下代码定义了一个Person类:publicclassPerson{   privateStringname;   private......