zoukankan      html  css  js  c++  java
  • 马拉车练习1

    前言 记录下今天写的2个马拉车的习题

    题目1

    含义

    给出一个字符串 s。求s 有多少对相交的回文子串。包含也算作相交。

    思路

    这个居然有2900有点离谱

    本质上就是差分+马拉车的思想

    正难则反,我们可以统计不相交的回文子串的对数,然后用总对数减去不相交的回文子串的对数即是答案

    不相交的回文子串假设端点分别为 \(x1,y1,x2,y2\) 那么一定有$ x1\leq y1 <\ x2 \leq y2$

    我们只要统计出以i 为起点的回文串个数 st[i],和以 i为终点的回文串个数 ed[i]。然后计算

    \(\sum\limits_{i=1}^{n} ed[i] \sum\limits_{j=i+1}^nst[j]\)

    在使用 manacher 算法的时候,对每个 i 都计算出了 p[i] 那么我们就要把[i-p[i]+1, i+p[i]-1] 这个极大回文子串

    对 st和 ed 的贡献算进去。差分维护即可

    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    //typedef pair<int,int> pii;
    #define fi first
    #define se second
    #define debug printf("aaaaaaaaaaa\n");
    const int maxn=4e6+5,inf=0x3f3f3f3f,mod=51123987;
    const ll INF=0x3f3f3f3f3f3f3f3f;
    const double eps=1e-7;
    char temp[maxn];
    char s[maxn];
    int n,tempn;
    int p[maxn];
    ll pre1[maxn],pre2[maxn];
    int main(){
        scanf("%d %s",&tempn,temp+1);
        n=2*tempn+1;
        s[0]='~';
        for(int i=1;i<=n;i++){
            if(i%2==0) s[i]=temp[i/2];
            else s[i]='|';
        }
        int id=1,mx=1;
        ll ans=0;
        for(int i=1;i<=n;i++){
            if(mx>=i){
                p[i]=min(p[2*id-i],mx-i+1);
            }
            while(s[i+p[i]]==s[i-p[i]]) p[i]++;
            if(p[i]+i-1>=mx){
                mx=p[i]+i-1;
                id=i;
            }
            ans+=((p[i]-1)+1)/2;
            if(s[i]=='|'&&p[i]==1) continue;
            pre1[i-p[i]+1]++;
            pre1[i+1]--;
            pre2[i]++;
            pre2[i+p[i]]--;
        }
        ans%=mod;
        ans=ans*(ans-1)/2%mod;
        for(int i=1;i<=n;i++){
            pre1[i]+=pre1[i-1];
            pre2[i]+=pre2[i-1];
        }
        for(int i=1;i<=n;i++){
            if(i%2==1) pre2[i]=pre1[i]=0;
        }
        for(int i=n-1;i>=1;i--){ // 终点
            pre1[i]+=pre1[i+ 1];
            pre1[i]=(pre1[i]%mod+mod)%mod;
        }
        for(int i=1;i<=n;i++){
            if(s[i]=='|'&&i-1>=1&&i+1<=n){
                ans-=(pre2[i-1]*pre1[i+1])%mod;
                ans%=mod;
            }
        }
        printf("%lld\n",(ans%mod+mod)%mod);
        return 0;
    }
    

    题目2

    含义

    定义双回文串 T,满足存在 T = ab,其中 a 和 b 都是回文串。给定字符串 S,求一个 S 的最长的双回文子T。

    |S| ≤ 10^6

    思路

    我们可以枚举 ab 中间的分界线。然后两边分别计算能延伸出去的最长回文子串长度,然后相加即可

    维护以\(i\)开头的最长回文串,维护以\(i\)结尾的最长回文串

    很容易想到使用马\(O(n^2)\)的算法,但其实对于每个\(i\)可以只更新以\(i\)为中点的回文串的边界点的值

    然后最后再遍历一边rr[i]=max(rr[i],rr[i−2]−2)和ll[i]=max(ll[i],ll[i+2]−2)

    给那些被半径直接略过的位置赋值,如:aabaa可能第五个位置ll被赋值,但第四个位置的a未被赋值。。。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    typedef pair<int,int> pii;
    #define fi first
    #define se second
    #define debug printf("aaaaaaaaaaa\n");
    const int maxn=2e5+5,inf=0x3f3f3f3f,mod=51123987;
    const ll INF=0x3f3f3f3f3f3f3f3f;
    const double eps=1e-7;
    char s[maxn],t[maxn];
    int ns,nt;
    int bg[maxn],ed[maxn];
    int p[maxn];
    int main(){
        scanf("%s",s+1);
        ns=strlen(s+1);
        nt=2*ns+1;
        t[0]='~';
        for(int i=1;i<=nt;i++){
            if(i%2){
                t[i]='|';
            }else{
                t[i]=s[i/2];
            }
        }
        int mx=1,id=1;
        for(int i=1;i<=nt;i++){
            if(mx>=i) p[i]=min(mx-i+1,p[2*id-i]);
            while(t[i+p[i]]==t[i-p[i]]) p[i]++;
            if(i+p[i]-1>=mx) mx=i+p[i]-1,id=i;
            bg[i-p[i]+2]=max(bg[i-p[i]+2],p[i]-1);
            ed[i+p[i]-2]=max(ed[i+p[i]-2],p[i]-1);
        }
        for(int i=2;i<=nt;i+=2){
            bg[i]=max(bg[i],bg[i-2]-2);
        }
        for(int i=nt-2;i>=2;i-=2){
            ed[i]=max(ed[i],ed[i+2]-2);
        }
        int ans=0;
        for(int i=2;i<=nt-2;i+=2){
            ans=max(ans,ed[i]+bg[i+2]);
        }
        printf("%d\n",ans);
        return 0;
    }
    
    不摆烂了,写题
  • 相关阅读:
    用带缓冲区的文件流FileStream来实现大文件的拷贝
    委托与事件、匿名方法与Lambda表达式
    C#基础点记录
    C#基础知识06
    用while语句实现用户登陆程序
    TSQL检索电话呼叫员的工作流水信息
    委托
    类型转换、异常、String知识总结
    内网入库单管理系统
    用C#打印出正等腰三角形
  • 原文地址:https://www.cnblogs.com/hunxuewangzi/p/15068172.html
Copyright © 2011-2022 走看看