2019牛客多校第四场

C sequence

题意

给定序列\(a\)\(b\),求所有区间\(min\{a_l,a_{l+1}...a_r\}*sum\{b_l,b_{l+1}...b_r\}\)

分析

预处理出每个\(a_i\)作为最小值延伸的区间,然后线段树维护\(b\)序列,对于每个\(a_i\),根据正负,求出包含\(b_i\)的最大子段和和最小子段和。

代码

#include <bits/stdc++.h>
using namespace std;
#define ls i<<1
#define rs i<<1|1
#define mid (l+r)/2
typedef long long ll;
const int N=3e6+15;
int n,a[N],b[N],le[N],ri[N],q[N];
struct node{
    ll sum,lmx,rmx;
}tr[N*4];
void pushup(int i){
    tr[i].sum=tr[ls].sum+tr[rs].sum;
    tr[i].lmx=max(tr[ls].lmx,tr[ls].sum+tr[rs].lmx);
    tr[i].rmx=max(tr[rs].rmx,tr[rs].sum+tr[ls].rmx);
}
void build(int i,int l,int r){
    if(l==r){
        tr[i].sum=tr[i].lmx=tr[i].rmx=b[l];
        return;
    }
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup(i);
}
node query(int i,int l,int r,int ql,int qr){
    if(ql<=l && qr>=r){
        return tr[i];
    }
    if(qr<=mid){
        return query(ls,l,mid,ql,qr);
    }
    if(ql>mid){
        return query(rs,mid+1,r,ql,qr);
    }
    node ll=query(ls,l,mid,ql,qr);
    node rr=query(rs,mid+1,r,ql,qr);
    node ans{};
    ans.sum=ll.sum+rr.sum;
    ans.lmx=max(ll.lmx,ll.sum+rr.lmx);
    ans.rmx=max(rr.rmx,rr.sum+ll.rmx);
    return ans;
}
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&b[i]);
    }
    int l=1;
    int l1=1,r1=0;
    for(int r=1;r<=n;r++){
        while(l1<=r1 && a[r]<a[q[r1]]){
            ri[q[r1]]=r-1;
            r1--;
        }
        q[++r1]=r;
        l++;
    }
    while(l1<=r1){
        ri[q[r1]]=n;
        r1--;
    }
    l=1;
    l1=1,r1=0;
    for(int r=n;r>=1;r--){
        while(l1<=r1 && a[r]<a[q[r1]]){
            le[q[r1]]=r+1;
            r1--;
        }
        q[++r1]=r;
        l++;
    }
    while(l1<=r1){
        le[q[r1]]=1;
        r1--;
    }
    ll ans=0;
    build(1,1,n);
    for(int i=1;i<=n;i++){
        if(a[i]<0){
            continue;
        }
        node lt=query(1,1,n,le[i],i);
        node rt=query(1,1,n,i,ri[i]);
        ll t=max(max(lt.rmx,rt.lmx),max(1ll*b[i],lt.rmx+rt.lmx-b[i]));
        ans=max(ans,t*a[i]);
    }
    for(int i=1;i<=n;i++){
        b[i]=-b[i];
    }
    build(1,1,n);
    for(int i=1;i<=n;i++){
        if(a[i]>0){
            continue;
        }
        node lt=query(1,1,n,le[i],i);
        node rt=query(1,1,n,i,ri[i]);
        ll t=max(max(lt.rmx,rt.lmx),max(1ll*b[i],lt.rmx+rt.lmx-b[i]));
        ans=max(ans,-t*a[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

I string

题意

给一个字符串,一个子串与其逆序子串算同一个,求子串个数。

分析

  • 如果不考虑其他条件,不同子串个数可以用后缀数组求出。因为要涉及到翻转过来的子串,所以按照后缀数组常见套路,将字符串逆序拼接在后面,求出子串个数,因为跨越中间的不算,判断一下\(sa[i]\)的大小即可。
  • 对于上一步求出的所有子串,如果子串不是回文串,那么每两个子串其实就是一个子串,另一个是由字符串翻转之后得到的,而如果子串是回文串,那么就只求出一个。
  • 因此我们用回文树求出所有本质不同的回文子串,加到上一步求出的所有子串当中,现在再除以二,就是满足题目要求的子串个数。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+50;
char str[N];
int s[N],sa[N],rk[N],h[N];
int t[N],t2[N],c[N];
void Sa(int n,int m=128){
    n++;
    int *x=t,*y=t2;
    for(int i=0;i<m;i++){
        c[i]=0;
    }
    for(int i=0;i<n;i++){
        c[x[i]=s[i]]++;
    }
    for(int i=1;i<m;i++){
        c[i]+=c[i-1];
    }
    for(int i=n-1;i>=0;i--){
        sa[--c[x[i]]]=i;
    }
    for(int k=1;k<=n;k<<=1){
        int p=0;
        for(int i=n-k;i<n;i++){
            y[p++]=i;
        }
        for(int i=0;i<n;i++){
            if(sa[i]>=k){
                y[p++]=sa[i]-k;
            }
        }
        for(int i=0;i<m;i++){
            c[i]=0;
        }
        for(int i=0;i<n;i++){
            c[x[y[i]]]++;
        }
        for(int i=1;i<m;i++){
            c[i]+=c[i-1];
        }
        for(int i=n-1;i>=0;i--){
            sa[--c[x[y[i]]]]=y[i];
        }
        swap(x,y);
        p=1;
        x[sa[0]]=0;
        for(int i=1;i<n;i++){
            x[sa[i]]=y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
        }
        if(p>=n){
            break;
        }
        m=p;
    }
    n--;
    for(int i=0;i<=n;i++){
        rk[sa[i]]=i;
    }
    int k=0;
    for(int i=0;i<n;i++){
        if(k){
            k--;
        }
        int j=sa[rk[i]-1];
        while(s[i+k]==s[j+k]){
            k++;
        }
        h[rk[i]]=k;
    }
}
ll getP(int n){
    ll ans=0;
    int mid=n/2;
    for(int i=1;i<=n;i++){
        if(sa[i]>mid){
            ans+=1ll*(n-sa[i]-h[i]);
        }else if(sa[i]==mid){
            continue;
        }else{
            ans+=max(0ll,1ll*(mid-sa[i]-h[i]));
        }
    }
    return ans;
}
struct PT{
    int next[N][26],fail[N],cnt[N],num[N],len[N];
    int S[N],last,id[N],n,p;
    int newnode(int l){
        for(int i=0;i<26;i++){
            next[p][i]=0;
        }
        cnt[p]=0;
        num[p]=0;
        len[p]=l;
        return p++;
    }
    void init(){
        p=0;
        newnode(0);
        newnode(-1);
        last=0;
        n=0;
        S[n]=-1;
        fail[0]=1;
    }
    int getFail(int x){
        while(S[n-len[x]-1]!=S[n]){
            x=fail[x];
        }
        return x;
    }
    void add(int c){
        c-='a';
        S[++n]=c;
        int cur=getFail(last);
        if(!next[cur][c]){
            int now=newnode(len[cur]+2);
            fail[now]=next[getFail(fail[cur])][c];
            num[now]=num[cur]+1;
            next[cur][c]=now;
        }
        last=next[cur][c];
        cnt[last]++;
        id[last]=n;
    }
    void count(){
        for(int i=p-1;i>=0;i--){
            cnt[fail[i]]+=cnt[i];
        }
    }
}ac;
int main(void){
//    freopen("in.txt","r",stdin);
    scanf("%s",str);
    int n=strlen(str);
    for(int i=0;i<n;i++){
        s[i]=str[i]-'a'+2;
    }
    s[n]=1;
    for(int i=0;i<n;i++){
        s[n+1+i]=str[n-1-i]-'a'+2;
    }
    Sa(n*2+1);
    ll p=getP(n*2+1);
    ac.init();
    for(int i=0;i<n;i++){
        ac.add(str[i]);
    }
    ll q=ac.p-2;
    ll ans=(p+q)/2;
    printf("%lld\n",ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/zxcoder/p/11256798.html