九下四月上旬日记

4.1

闲话

  • 中午才换的食堂,从 \(2\) 楼换到 \(3\) 楼了。
  • 下午到机房后,没看见 \(miaomiao\) ,但 \(huge\)\(field\) 轮流坐在教师机前。
  • 去吃晚饭的路上听见隔壁录播室在录音乐课。

做题纪要

CF573D Bear and Cavalry

luogu P4381 [IOI2008] Island

4.2

闲话

  • \(miaomiao\) 回来了。
  • 下午 @User-Unauthorized\(miaomiao\) 不知道签了什么东西,然后就回原班了。现在 \(3\) 机房只剩下 @Trump_ 还在补 \(whk\) 了。
  • @xrlong 也被“发配”到机房了。

做题纪要

CF1530E Minimax

CF580E Kefa and Watch

  • 类似 [ABC331F] Palindrome Query ,线段树维护哈希值即可。

    点击查看代码
    const ll base=13331,mod=1000000007;
    ll jc[100010],hs[15][100010];
    char s[100010];
    struct SegmentTree
    {
        ll l,r,sum,len,lazy;
    }tree[400010];
    ll lson(ll x)
    {
        return x*2;
    }
    ll rson(ll x)
    {
        return x*2+1;
    }
    void pushup(ll rt)
    {
        tree[rt].sum=(tree[lson(rt)].sum*jc[tree[rson(rt)].len]%mod+tree[rson(rt)].sum)%mod;
        tree[rt].len=tree[lson(rt)].len+tree[rson(rt)].len;
    }
    void build(ll rt,ll l,ll r)
    {
        tree[rt].l=l;
        tree[rt].r=r;
        tree[rt].lazy=10;
        if(l==r)
        {
            tree[rt].sum=s[l]-'0';
            tree[rt].len=1;
            return ;
        }
        ll mid=(l+r)/2;
        build(lson(rt),l,mid);
        build(rson(rt),mid+1,r);
        pushup(rt);
    }
    void pushdown(ll rt)
    {  
        if(tree[rt].lazy!=10)
        {
            tree[lson(rt)].lazy=tree[rt].lazy;
            tree[rson(rt)].lazy=tree[rt].lazy;
            tree[lson(rt)].sum=hs[tree[rt].lazy][tree[lson(rt)].len];
            tree[rson(rt)].sum=hs[tree[rt].lazy][tree[rson(rt)].len];
            tree[rt].lazy=10;
        }
    }
    void update(ll rt,ll l,ll r,ll val)
    {
        if(l<=tree[rt].r&&tree[rt].l<=r)
        {
            if(l<=tree[rt].l&&tree[rt].r<=r)
            {
                tree[rt].lazy=val;
                tree[rt].sum=hs[val][tree[rt].len];
                return ;
            }
            pushdown(rt);
            update(lson(rt),l,r,val);
            update(rson(rt),l,r,val);
            pushup(rt);
        }
    }
    pair<ll,ll> query(ll rt,ll l,ll r)
    {
        if(r<tree[rt].l||tree[rt].r<l)
        {
            return make_pair(0,0); 
        }
        if(l<=tree[rt].l&&tree[rt].r<=r)
        {
            return make_pair(tree[rt].sum,tree[rt].len);
        }
        pushdown(rt);
        pair<ll,ll>lans=query(lson(rt),l,r),rans=query(rson(rt),l,r);
        return make_pair((lans.first*jc[rans.second]%mod+rans.first)%mod,lans.second+rans.second);
    }
    int main()
    {
        ll n,m,k,pd,l,r,val,i,j;
        cin>>n>>m>>k>>(s+1);
        for(i=0;i<=n;i++)
        {
            jc[i]=(i==0)?1:jc[i-1]*base%mod;
        }
        for(i=0;i<=9;i++)
        {
            for(j=1;j<=n;j++)
            {
                hs[i][j]=(hs[i][j-1]*base%mod+i)%mod;
            }
        }
        build(1,1,n);
        for(i=1;i<=m+k;i++)
        {
            cin>>pd>>l>>r>>val;
            if(pd==1)
            {
                update(1,l,r,val);
            }
            else
            {
                if(r-l+1==val||query(1,l,r-val).first==query(1,l+val,r).first)
          {
            cout<<"YES"<<endl;
          }
          else
          {
            cout<<"NO"<<endl;
          }
            }
        }
        return 0;
    }
    

4.3

闲话

  • 起床后发现下雨了,遂没跑操,直接去吃饭了。
  • 一个早读 \(5\) 例通报,给现班主任整急了,把通报的人交出去 \(D\) 了半节多课,让下午象征性地停了 \(20min\) 的奥赛课。

做题纪要

CF955D Scissors

CF1400F x-prime Substrings

4.4

闲话

  • 学校清明不给放假,已经在期待五一学校能放几天了。
  • 早上现班主任见 @xrlong 被“发配”到机房了,所以把他桌子和 @wkh2008 放到一起了。也让 @wang54321 从我后面搬到了我左后方,这下我后面没桌子了。
  • 晚上现班主任开了小班会。强调了下常规;讲了下曾经学长拿的奖项,“你们现在学的东西高中 whk 不可能学到”,鼓了鼓劲;说周日公开班会一个环节是随机找学生讲历届学长的故事,以及自己是怎么认识/知道他(她)的;说 @wkh2008@xrlong 一直在机房,要看他们期中成绩。

做题纪要

luogu P3809 【模板】后缀排序

  • 后缀数组板子。

    • \(sa_{i}\) 表示将所有后缀排序后第 \(i\) 小的字符串的起始位置,即后缀数组; \(rk_{i}\) 表示从 \(s_{i \sim |s|}\) 的排名。二者满足 \(sa_{rk_{i}}=rk_{sa_{i}}=i\) 的关系。
    • 先对 \(s\) 所有长度为 \(1\) 的子串进行排序,得到 \(sa_{1},rk_{1}\) 。然后进行倍增,具体地,用长度为 \(\frac{w}{2}(2 \le w<n)\) 的子串的排名,即 \(rk_{\frac{w}{2},i},rk_{\frac{w}{2},i+\frac{w}{2}}\) 作为 \(s\) 的第 \(i\) 个长度为 \(w\) 的子串 \(s_{i \sim \min(i+w-1,|s|)}\) 的第一、第二个关键字,并对其进行排序,得到 \(sa_{w},rk_{w}\) ,其中当 \(i+w \ge |s|\) 时,规定 \(rk_{w,i+w}=0\)
      • \(id_{w,i}\) 表示将所有长度为 \(w\) 的后缀排序后第二关键字第 \(i\) 小的位置。
      • 由于 \(rk_{w,i}\) 表示 \(s_{i \sim \min(i+w-1,|s|)}\) 的排名,当 \(w \ge |s|\) 时,得到的 \(sa_{w}\) 即为我们需要的后缀数组 \(sa\)
    • 将单次 sort基数排序 优化,使单次排序复杂度由 \(O(n \log{n})\) 变为 \(O(n)\)
    • 总时间复杂度为 \(O(n \log{n})\)
    • 由于基数排序常数巨大,需要进行一定的常数优化。
      • 考虑第二关键字 \(rk_{\frac{w}{2},i+\frac{w}{2}}\) 排序的实质,是将超出字符串范围的放到前面,剩下的按照原顺序放回。
      • 若排名都不相同可直接生成后缀数组
        • 对于新的 \(rk\) ,若排名都不相同,即值域为 \([1,|s|]\) ,则可以直接生成后缀数组。
      • 优化计数排序的值域
      • 减少不连续内存访问
    点击查看代码
    int sa[1000010],rk[2000010],oldrk[2000010],id[1000010],cnt[1000010],key[1000010];
    char s[1000010];
    int val(char x)
    {
        return (int)x;
    }
    void counting_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(int i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(int i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],int len)
    {
        int m=127,tot=0,num=0,i,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)//m=tot 用来优化计数排序的值域
        //之所以不写 w<n ,是因为要确保长度为 1 的串进行一次后缀排序
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];//将 rk[id[i]] 存到 key[i] 里,来减少不连续内存访问,以达到卡常的效果
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
    }
    int main()
    {
        int n,i;
        cin>>(s+1);
        n=strlen(s+1);
        init_sa(s,n);
        for(i=1;i<=n;i++)
        {
            cout<<sa[i]<<" ";
        }
        return 0;
    }
    

luogu P4051 [JSOI2007] 字符加密

  • 考虑破环为链,将 \(s\) 复制一遍,得到 \(ss\)

  • 手摸样例,得到后缀排序如下,有 I0O7SJ 分别出现在排名为 \(2,4,6,8,10,12\) 的第 \(6\) 个位置。

    点击查看后缀排序
    1:07
    2:07JSOI07
    3:7
    4:7JSOI07
    5:I07
    6:I07JSOI07
    7:JSOI07
    8:JSOI07JSOI07
    9:OI07
    10:OI07JSOI07
    11:SOI07
    12:SOI07JSOI07
    
  • 分别取 \(sa_{i} \le |s|\)\(s_{sa_{i}+|s|-1}\) 即可。

    点击查看代码
    int sa[200010],rk[400010],oldrk[400010],id[200010],cnt[200010],key[200010];
    char s[200010];
    int val(char x)
    {
        return (int)x;
    }
    void counting_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(int i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(int i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],int len)
    {
        int m=127,tot=0,num=0,i,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
    }
    int main()
    {
        int n,i;
        cin>>(s+1);
        n=strlen(s+1);
        for(i=1;i<=n;i++)
        {
            s[i+n]=s[i];
        }
        n*=2;
        init_sa(s,n);
        for(i=1;i<=n;i++)
        {
            if(sa[i]<=n/2)
            {
                cout<<s[sa[i]+n/2-1];
            }
        }
        return 0;
    }
    

UVA1223 Editor

  • \(height\) 板子。

    • \(LCP(x,y)\) 表示字符串 \(x\)\(y\) 的最长公共前缀的长度。
      • \(LCP\) 相关性质
        • 性质 \(1\)\(LCP(s_{x \sim |s|},s_{y \sim |s|})=\min\limits_{x \le i<j<k \le y} \{ LCP(s_{i \sim |s|},s_{j \sim |s|}),LCP(s_{j \sim |s|},s_{k \sim |s|}) \}\)
          • 证明
            • \(\min\{ LCP(s_{i \sim |s|},s_{j \sim |s|}),LCP(s_{j \sim |s|},s_{k \sim |s|}) \}=p>0\) ,则有 \(s_{i+l}=s_{j+l}=s_{k+l}(0 \le l \le p-1)\)
            • 容易有 \(LCP(s_{i \sim |s|},s_{k \sim |s|})=p\) ,进一步可以扩展到 \(LCP(s_{x \sim |s|},s_{y \sim |s|})\)
              • \(LCP(s_{i \sim |s|},s_{k \sim |s|})=p\) 可通过反证法证明。
        • 性质 \(2\)\(\begin{cases} LCP(s_{sa_{x} \sim |s|},s_{sa_{y} \sim |s|})=\min\limits_{i=x+1}^{y} \{ LCP(s_{sa_{i} \sim |s|},s_{sa_{i-1} \sim |s|}) \}=\min\limits_{i=x+1}^{y} \{ height_{i} \} \\ LCP(s_{x \sim |s|},s_{y \sim |s|})=\min\limits_{i=rk_{x}+1}^{rk_{y}} \{ LCP(s_{sa_{i} \sim |s|},s_{sa_{i-1} \sim |s|}) \}=\min\limits_{i=rk_{x}+1}^{rk_{y}} \{ height_{i} \} \end{cases}\)
    • \(height_{i}\) 表示 \(LCP(s_{sa_{i} \sim |s|},s_{sa_{i-1} \sim |s|})\) ,即第 \(i\) 名的后缀和第 \(i-1\) 名的后缀的最长公共前缀的长度。特别地,有 \(height_{1}=0\)
    • \(O(n)\)\(height\) 需要的结论: \(height_{rk_{i}} \ge height_{rk_{i-1}}-1\) 。故求 \(height_{rk_{i}}\) 时继承 \(height_{rk_{i-1}}-1\) 得到下限即可。
  • 最终,有 \(\max\limits_{i=1}^{|s|} \{ height_{i} \}\) 即为所求。

    点击查看代码
    int sa[5010],rk[10010],oldrk[10010],id[5010],cnt[5010],key[5010],height[5010];
    char s[5010];
    int val(char x)
    {
        return (int)x;
    }
    void counting_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(int i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(int i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],int len)
    {
        int m=127,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])//前面不写成 sa[rk[i]] 来减少不连续内存访问
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    int main()
    {
        int t,n,ans,i,j;
        cin>>t;
        for(j=1;j<=t;j++)
        {
            ans=0;
            cin>>(s+1);
            n=strlen(s+1);
            init_sa(s,n);
            for(i=1;i<=n;i++)
            {
                ans=max(ans,height[i]);
            }
            cout<<ans<<endl;
        }
        return 0;
    }
    

4.5

闲话

  • @wkh2008@xrlong 被现班主任拉来跑早操了,但课间操还是不跑。
  • 早上 \(miaomiao\) 问我们第一节课到底是啥,问我们当时咋换的课。上完第一节课回去的路上碰见了 @Vsinger_洛天依 ,称之为“交换场地”。
  • 上午现教处见学校公众号没素材了,就趁我们上体育课拍了点素材发公众号了,我、 @Charlie_ljk@Pursuing_OIer 被拍了。
  • \(miaomiao\) 下午 \(13:50 \sim 18:05\) 安排了一场模拟赛。
  • \(VScode\)Competitive Programming Helper (cph) 插件测了几发大样例,感觉还行,懒得在终端敲 diff filename1.xxx filename2.xxx 了。

做题纪要

luogu P2852 [USACO06DEC] Milk Patterns G

  • 一个子串出现至少 \(k\) 次等价于有至少连续 \(k\) 个后缀以这个子串作为公共前缀。

  • 最终,有 \(\max\limits_{i=k-1}^{|s|} \{ \min\limits_{j=i-(k-1)}^{i} \{ height_{j} \} \}\) 即为所求,单调队列维护即可。

    点击查看代码
    int s[1000010],sa[1000010],rk[2000010],oldrk[2000010],id[1000010],cnt[1000010],key[1000010],height[1000010];
    deque<int>q;
    void counting_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(int i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(int i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(int s[],int len)
    {
        int m=1000000,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=s[i];
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    int main()
    {
        int n,k,ans=0,i;
        cin>>n>>k;
        k--;
        for(i=1;i<=n;i++)
        {
            cin>>s[i];
        }
        init_sa(s,n);
        for(i=1;i<=n;i++)
        {
            while(q.empty()==0&&height[q.front()]>=height[i])
            {
                q.pop_front();
            }
            q.push_front(i);
            while(q.empty()==0&&q.back()<=i-k)
            {
                q.pop_back();
            }
            if(i>=k)
            {
                ans=max(ans,height[q.back()]);
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P4248 [AHOI2013] 差异

  • 推式子,有 \(\begin{aligned} \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}|s_{i \sim |s|}|+|s_{j \sim |s|}| &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}(n-i+1)+(n-j+1) \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}2(n+1)-\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}i-\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}j \\ &=\sum\limits_{i=1}^{n}2(n+1)(n-i)-\sum\limits_{i=1}^{n}i(n-i)-\sum\limits_{i=1}^{n}i(i-1) \\ &=2(n+1)\sum\limits_{i=1}^{n}(n-i)-(n-1)\sum\limits_{i=1}^{n}i \\&=n(n+1)(n-1)-\frac{n(n+1)(n-1)}{2} \\ &=\frac{n(n+1)(n-1)}{2} \end{aligned}\)

  • \(LCP\) 性质 \(2\) ,有 \(LCP(s_{sa_{i} \sim |s|,sa_{j} \sim |s|})=\min\limits_{k=i+1}^{j} \{ height_{k} \}\) 。然后有 \(\begin{aligned} \sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{i \sim |s|,j \sim |s|}) &=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{sa_{i} \sim |s|,sa_{j} \sim |s|}) \\&=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}\min\limits_{k=i+1}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=2}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}\min\limits_{j=1}^{i} \{ height_{j} \} \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}-\sum\limits_{i=1}^{n}0 \\ &=\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \} \end{aligned}\)

  • \(\sum\limits_{i=1}^{n}\sum\limits_{j=i}^{n}\min\limits_{k=i}^{j} \{ height_{k} \}\) 可用单调栈维护。

    点击查看代码
    ll sa[500010],rk[1000010],oldrk[1000010],id[500010],cnt[500010],key[500010],height[500010],f[500010];
    char s[500010];
    stack<ll>st;
    ll val(char x)
    {
        return (ll)x;
    }
    void counting_sort(ll n,ll m)
    {
        memset(cnt,0,sizeof(cnt));
        for(ll i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(ll i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(ll i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],ll len)
    {
        ll m=127,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    int main()
    {
        ll n,sum=0,i;
        cin>>(s+1);
        n=strlen(s+1);
        init_sa(s,n);
        for(i=1;i<=n;i++)
        {
            while(st.empty()==0&&height[st.top()]>=height[i])
            {
                st.pop();
            }
            if(st.empty()==0)
            {
                f[i]=f[st.top()]+height[i]*(i-st.top());
            }
            else
            {
                f[i]=f[0]+height[i]*(i-0);
            }
            st.push(i);
            sum+=f[i];
        }
        cout<<n*(n+1)*(n-1)/2-2*sum<<endl;
        return 0;
    }
    

tgHZOJ 5663.最后一课

luogu P2408 不同子串个数

  • 多倍经验: SP694 DISUBSTR - Distinct Substrings | SP705 SUBST1 - New Distinct Substrings

  • 对于两个子串,其重复的前缀数等于其最长公共前缀的长度。对于一个后缀 \(s_{sa_{i} \sim n}\) ,它产生了 \(n-sa_{i}+1\) 个前缀,和 \(s_{sa_{i-1} \sim n}\) 相比产生了 \(height_{i}\) 个相同的前缀,则会产生 \(n-sa_{i}+1-height_{i}\) 个不同的子串。

  • 故最终 \(\begin{aligned} \sum\limits_{i=1}^{n}n-sa_{i}+1-height_{i} &=n(n+1)-\sum\limits_{i=1}^{n}sa_{i}-\sum\limits_{i=1}^{n}height_{i} \\ &=n(n+1)-\frac{n(n+1)}{2}-\sum\limits_{i=1}^{n}height_{i} \\ &=\frac{n(n+1)}{2}-\sum\limits_{i=1}^{n}height_{i} \end{aligned}\) 即为所求。

    点击查看代码
    ll sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010],height[100010],a[100010],b[100010],fminn[100010][20];
    char s[100010];
    ll val(char x)
    {
        return (ll)x;
    }
    void counting_sort(ll n,ll m)
    {
        memset(cnt,0,sizeof(cnt));
        for(ll i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(ll i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(ll i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],ll len)
    {
        ll m=127,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    int main()
    {
        ll n,sum=0,i;
        cin>>n>>(s+1);
        init_sa(s,n);
        for(i=1;i<=n;i++)
        {
            sum+=height[i];
        }
        cout<<n*(n+1)/2-sum<<endl;
        return 0;
    }
    

AT_s8pc_2_e 部分文字列

  • 对于一个后缀 \(s_{sa_{i} \sim n}\) ,它产生了 \(n-sa_{i}+1\) 个前缀,其长度和为 \(\frac{(n-sa_{i}+1)(n-sa_{i}+2)}{2}\) ;和 \(s_{sa_{i-1} \sim n}\) 相比产生了 \(height_{i}\) 个相同的前缀,其长度和为 \(\frac{height_{i}(height_{i}+1)}{2}\)

  • 最终,有 \(\begin{aligned} \sum\limits_{i=1}^{n}\frac{(n-sa_{i}+1)(n-sa_{i}+2)}{2}-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} &=\sum\limits_{i=1}^{n}\frac{(n-i+1)(n-i+2)}{2}-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} \\ &=\sum\limits_{i=1}^{n}\frac{i(i+1)}{2}-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} \\ &=\frac{1}{2} \times (\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2})-\sum\limits_{i=1}^{n}\frac{height_{i}(height_{i}+1)}{2} \end{aligned}\) 即为所求。

    点击查看代码
    ll sa[100010],rk[200010],oldrk[200010],id[100010],cnt[100010],key[100010],height[100010],a[100010],b[100010],fminn[100010][20];
    char s[100010];
    ll val(char x)
    {
        return (ll)x;
    }
    void counting_sort(ll n,ll m)
    {
        memset(cnt,0,sizeof(cnt));
        for(ll i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(ll i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(ll i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],ll len)
    {
        ll m=127,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    int main()
    {
        ll n,sum=0,i;
        scanf("%s",s+1);
        n=strlen(s+1);
        init_sa(s,n);
        for(i=1;i<=n;i++)
        {
            sum+=height[i]*(height[i]+1)/2;
        }
        cout<<(n*(n+1)*(2*n+1)/6+n*(n+1)/2)/2-sum<<endl;
        return 0;
    }
    

4.6

闲话

  • 因高一要远足,早上占了大操场,被迫去小操场跑操,要求的脚步忽快忽慢,难崩;下午占了大操场,初中的都去小操场放假。
  • \(1\) 节课前现班主任说因放假占了下午一节课,所以上午第 \(5\) 节课最好上奥赛自习,然后第 \(4\) 节课 @lty_ylzsx 直接就把“公”改成了“A”,我们直接就去机房了。到机房后 \(miaomiao\) 问了我们明天什么时间返校。
  • 因桌子旁边太多书被现班主任 \(D\) 了。
  • 下午到机房后,发现所有人又双叒叕被关机了。后因 @wkh2008 等人过于“放飞自我”被 \(miaomiao\) \(D\) 了。

做题纪要

tgHZOJ 5664. 日常

tgHZOJ 5665. 渡尘

4.7

闲话

  • 下午返校后收拾了下东西,然后直接来机房了,到机房后发现没有 \(miaomiao\)
  • 因说话声音过大,被 \(feifei\) \(D\) 了,“好好做题啊,不要总说话”。
  • @STA_Morlin 限时返厂,想来机房用【数据删除】进行 【数据删除】 ,但因为有 【数据删除】 ,所以又拿着 【数据删除】 走了。
  • \(16:30\)\(miaomiao\) 使用前方高级摄像头隔空喊话,称现班主任找我们,然后让我们回班。回班后被现班主任 \(D\) 了,称没有教练去机房怎么保证学习效率,以后再有这事就停我们课(因为不是第一次了)。
  • 晚上开公开班会,原班主任一进来就问我坐在哪里,然后坐在了我后面,问我期中打算考多少,还看见我书下面压的《西游记》了。开班会的时候有几个互动的环节,包括但不限于统计中午留下学习的、递交并随机找人念挑战书,尬死我了。班会上说了下我们对于年级、学校的引领等作用,喂了点鸡汤;给各班主任说我们平常上(奥赛)课非常专注,奥赛三节连堂属于正常现象。

做题纪要

4.8

闲话

  • 貌似 \(miaomiao\) 还没回来。
  • 下午 \(field\) 说周日 \(13:00 \sim 18:00\) 有北京交通大学第十八届大学生程序设计竞赛(线上同步赛),让我们注册了账号,高校和院校填的 HZ ,到时候统一打。同时开了会全网,存了下 luogu 的 cookie 。
    • 邀请各位兄弟院校新生友情参赛! 这下 HZ 成北交大兄弟院校了。
    • 比赛链接
    • @Pursuing_OIer 翻程序设计评测常见问题及说明时翻到了 本站评测环境未开启忽略行末空格和文件尾换行,因此如果答案正确,但格式存在问题则会出现Presentation Error的评测结果。请注意严格按照题目的要求输入输出。 ,有点抽象。

做题纪要

luogu P6066 [USACO05JAN] Watchcow S

  • 无向图欧拉回路板子。

    点击查看代码
    int id[100010];
    vector<pair<int,int> >e[100010];
    stack<int>s;
    void dfs(int x)
    {
        for(int i=id[x];i<e[x].size();i=max(i+1,id[x]))
        {
            if(e[x][i].second==0)
            {
                id[x]=i+1;
                e[x][i].second=1;            
                dfs(e[x][i].first);
            }
        }
        s.push(x);
    }
    int main()
    {
        int n,m,u,v,i;
        cin>>n>>m;
        for(i=1;i<=m;i++)
        {   
            cin>>u>>v;
            e[u].push_back(make_pair(v,0));
            e[v].push_back(make_pair(u,0));
        }
        dfs(1);
        while(s.empty()==0)
        {
            cout<<s.top()<<endl;
            s.pop();
        }
        return 0;
    }
    

luogu P2659 美丽的序列

  • \([l_{i},r_{i}]\) 表示满足 \(\min\limits_{k=l_{i}}^{r_{i}} \{ a_{ k} \}=a_{i}\) 的最大区间,单调栈维护即可。

    点击查看代码
    ll a[2000010],l[2000010],r[2000010];
    stack<ll>s1,s2;
    int main()
    {
        ll n,ans=0,i;
        cin>>n;
        for(i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        for(i=1;i<=n;i++)
        {
            l[i]=i;
            while(s1.empty()==0&&a[s1.top()]>=a[i])
            {
                l[i]=l[s1.top()];
                s1.pop();
            }
            s1.push(i);
        }
        for(i=n;i>=1;i--)
        {
            r[i]=i;
            while(s2.empty()==0&&a[s2.top()]>=a[i])
            {
                r[i]=r[s2.top()];
                s2.pop();
            }
            s2.push(i);
        }
        for(i=1;i<=n;i++)
        {
            ans=max(ans,(r[i]-l[i]+1)*a[i]);
        }
        cout<<ans<<endl;
        return 0;
    }
    

tgHZOJ 5666. 罪人挽歌

4.9

闲话

  • 早上起床后发现昨天晚上下雨了,跑道是湿的,没办法跑操了,但还是要去操场集合、候操、听中考动员。
  • 上午物理老师说因周日正常周测所以期中提前到了周三晚上,但这周六没有家长会,体活改学生大会,周日上午第 \(5\) 节改体活。数学老师说昨天主任开会的时候说奥赛生 \(whk\) 不能掉太多,中考还指望我们出状元呢。
  • 下午到机房后发现 \(miaomiao\) 回来了。 \(miaomiao\) 看模拟赛 \(T4\) 大部分人都过了,就不组织讲题了,让不会的人找别人问问。
  • 隔壁录播室又在录课, \(huge\) 过来维持了下秩序。

做题纪要

luogu P5546 [POI2000] 公共串

  • 多倍经验: SP1811 LCS - Longest Common Substring | SP1812 LCS2 - Longest Common Substring II | SP10570 LONGCS - Longest Common Substring

  • \(s_{1} \sim s_{n}\) 拼起来得到 \(t=s_{1}s'_{1}s_{2}s'_{2} \dots s_{n}s'_{n}\) ,其中 \(s'\) 表示分隔符,且 \(s'_{1}<s'_{2}<\dots<s'_{n}\) 。由 luogu P2852 [USACO06DEC] Milk Patterns G ,有 \(\max\limits_{1 \le l<r \le |t|} \{ \min\limits_{i=l+1}^{r} \{ height_{i} \} \}\) 即为所求,其中排名在 \([l,r]\) 中的后缀包含了所有字符串,即对于任意一个 \(s_{i}\) 均有一个后缀的排名在 \([l,r]\) 中 。用双指针加单调队列维护。

    点击查看代码
    int sa[11000],rk[21000],oldrk[21000],id[11000],cnt[11000],key[11000],height[11000],L[11000],R[11000],c[11000],vis[11000];
    char s[11000],t[11000];
    deque<int>q;
    int val(int x)
    {
        return (char)x;
    }
    void counting_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(int i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(int i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],int len)
    {
        int m=127,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    void add(int l,int &sum)
    {
        if(c[l]!=0)//特判分隔符
        {
            vis[c[l]]++;
            sum+=(vis[c[l]]==1);
        }
    }
    void del(int l,int &sum)
    {
        if(c[l]!=0)
        {
            vis[c[l]]--;
            sum-=(vis[c[l]]==0);
        }
    }
    int main()
    {
       int m,n=0,ans=0,sum=0,l,r,i,j;
        cin>>m;
        for(i=1;i<=m;i++)
        {
            cin>>(t+1);
            L[i]=n+1;
            for(j=1;j<=strlen(t+1);j++)
            {
                n++;
                s[n]=t[j];
            }
            R[i]=n;
            n++;
            s[n]=i+'0';//分隔符视字符集而定
        }
        init_sa(s,n);
        for(i=1;i<=m;i++)
        {
            for(j=L[i];j<=R[i];j++)
            {
                c[rk[j]]=i;
            }
        }
        add(1,sum);
        for(l=1,r=2;r<=n;r++)
        {
            while(q.empty()==0&&height[q.front()]>=height[r])
            {
                q.pop_front();
            }
            q.push_front(r);
            add(r,sum);
            if(sum==m)
            {
                while(sum==m&&l<=r-1)
                {
                    del(l,sum);
                    l++;
                }
                l--;
                add(l,sum);
            }
            while(q.empty()==0&&q.back()<=l)
            {
                q.pop_back();
            }
            if(sum==m)
            {
                ans=max(ans,height[q.back()]);
            }
        }
        cout<<ans<<endl;
        return 0;
    }
    

luogu P2463 [SDOI2008] Sandy 的卡片

  • 差分后就转化成了 luogu P5546 [POI2000] 公共串

  • 由于得到的差分序列长度等于原串长度减一,所以最终统计答案的时候要加回来。

    点击查看代码
    int s[2100000],t[2100000],sa[2100000],rk[4100000],oldrk[4100000],id[2100000],cnt[2100000],key[2100000],height[2100000],L[2100000],R[2100000],c[2100000],vis[2100000];
    deque<int>q;
    int val(int x)
    {
        return (int)x;
    }
    void counting_sort(int n,int m)
    {
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(int i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(int i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(int s[],int len)
    {
        int m=5000,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    void add(int l,int &sum)
    {
        if(c[l]!=0)
        {
            vis[c[l]]++;
            sum+=(vis[c[l]]==1);
        }
    }
    void del(int l,int &sum)
    {
        if(c[l]!=0)
        {
            vis[c[l]]--;
            sum-=(vis[c[l]]==0);
        }
    }
    int main()
    {   
        int m,mm,n=0,ans=0,sum=0,l,r,i,j;
        cin>>m;
        for(i=1;i<=m;i++)
        {
            cin>>mm;
            L[i]=n+1;
            for(j=1;j<=mm;j++)
            {
                cin>>t[j];
                if(j>=2)
                {
                    n++;
                    s[n]=t[j]-t[j-1];
                }
            }
            R[i]=n;
            n++;
            s[n]=1864+i;
        }
        for(i=1;i<=n;i++)
        {
            s[i]+=1864;//保证 s[i] 不为负数,防止 RE
        }
        init_sa(s,n);
        for(i=1;i<=m;i++)
        {
            for(j=L[i];j<=R[i];j++)
            {
                c[rk[j]]=i;
            }
        }
        add(1,sum);
        for(l=1,r=2;r<=n;r++)
        {
            while(q.empty()==0&&height[q.front()]>=height[r])
            {
                q.pop_front();
            }
            q.push_front(r);
            add(r,sum);
            if(sum==m)
            {
                while(sum==m&&l<=r-1)
                {
                    del(l,sum);
                    l++;
                }
                l--;
                add(l,sum);
            }
            while(q.empty()==0&&q.back()<=l)
            {
                q.pop_back();
            }
            if(sum==m)
            {
                ans=max(ans,height[q.back()]);
            }
        }
        cout<<ans+1<<endl;
        return 0;
    }
    

4.10

闲话

  • 体育课后是历史,历史课前,历史老师说上周班会他听得心潮澎湃的,班会上说我们上课非常专注,为啥课前的课件这么吵。
  • 下午到机房后发现隔壁录播室在录高二语文课,找了些高二的来听。
  • \(miaomiao\) 在教师机坐了一会就走了,然后换成了 \(field\) 坐在前面。
  • \(field\) 催了催做 vjudge 的题。
  • 因晚上要考期中,所以下午第 \(10\) 节课回班收拾考场。
  • 晚上是语文和公共自习。
    • 语文
      • “黄发垂髫”代指小孩和老人 的说法错误, “黄发垂髫”代指老人和小孩 的说法错误。
      • 一句话概括主要内容 真的是要“一句话”,必须是纯主谓宾的结构,不能有并列分句。
      • 文言文是苏轼的《和桃源诗序》,因学校的高级印刷机,把 印成了 ,然后就有了 生不识盐酸
      • 记叙文一个概括内容的一问的标准答案为 祖母用扇子为“我”驱蚊 ,但这部分在原文仅出现了一句话,绷不住。

做题纪要

luogu P3181 [HAOI2016] 找相同字符

  • \(f(s)\) 表示 \(s\) 中位置不同大小相同的子串个数,由 luogu P4248 [AHOI2013] 差异\(f(s)=\sum\limits_{i=1}^{n}\sum\limits_{j=i+1}^{n}LCP(s_{i \sim |s|},s_{j \sim |s|})\)

  • 最终,有 \(f(s_{1} \& s_{2})-f(s_{1})-f(s_{2})\) 即为所求,其中 \(\&\) 为分隔符。

    点击查看代码
    ll sa[400010],rk[800010],oldrk[800010],id[400010],cnt[400010],key[400010],height[400010],f[400010];
    char s1[400010],s2[400010],s[400010];
    ll val(char x)
    {
        return (ll)x;
    }
    void counting_sort(ll n,ll m)
    {
        memset(cnt,0,sizeof(cnt));
        for(ll i=1;i<=n;i++)
        {
            cnt[key[i]]++;
        }
        for(ll i=1;i<=m;i++)
        {
            cnt[i]+=cnt[i-1];
        }
        for(ll i=n;i>=1;i--)
        {
            sa[cnt[key[i]]]=id[i];
            cnt[key[i]]--;
        }
    }
    void init_sa(char s[],ll len)
    {
        ll m=127,tot=0,num=0,i,j,w;
        for(i=1;i<=len;i++)
        {
            rk[i]=val(s[i]);
            id[i]=i;
            key[i]=rk[id[i]];
        }
        counting_sort(len,m);
        for(w=1;tot!=len;w<<=1,m=tot)
        {
            num=0;
            for(i=len;i>=len-w+1;i--)
            {
                num++;
                id[num]=i;
            }
            for(i=1;i<=len;i++)
            {
                if(sa[i]>w)
                {
                    num++;
                    id[num]=sa[i]-w;
                }
            }
            for(i=1;i<=len;i++)
            {
                key[i]=rk[id[i]];
            }
            counting_sort(len,m);
            for(i=1;i<=len;i++)
            {
                oldrk[i]=rk[i];
            }
            tot=0;
            for(i=1;i<=len;i++)
            {
                tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]);
                rk[sa[i]]=tot;
            }
        }
        for(i=1,j=0;i<=len;i++)
        {
            j-=(j>=1);
            while(s[i+j]==s[sa[rk[i]-1]+j])
            {
                j++;
            }
            height[rk[i]]=j;
        }
    }
    ll ask(char s[],ll len)
    {
        ll sum=0,i;
        stack<ll>st;
        init_sa(s,len);
        for(i=1;i<=len;i++)
        {
            while(st.empty()==0&&height[st.top()]>=height[i])
            {
                st.pop();
            }
            if(st.empty()==0)
            {
                f[i]=f[st.top()]+height[i]*(i-st.top());
            }
            else
            {
                f[i]=f[0]+height[i]*(i-0);
            }
            st.push(i);
            sum+=f[i];
        }
        return sum;
    }
    int main()
    {
        ll n1,n2,n=0,i;
        cin>>(s1+1)>>(s2+1);
        n1=strlen(s1+1);
        n2=strlen(s2+1);
        for(i=1;i<=n1;i++)
        {
            n++;
            s[n]=s1[i];
        }
        n++;
        s[n]='0';
        for(i=1;i<=n2;i++)
        {
            n++;
            s[n]=s2[i];
        }
        cout<<ask(s,n)-ask(s1,n1)-ask(s2,n2)<<endl;
        return 0;
    }
    

CF1073G Yet Another LCP Problem

posted @ 2024-04-07 10:00  hzoi_Shadow  阅读(100)  评论(2编辑  收藏  举报
扩大
缩小