2019CCPC网络赛(部分题解)

2019CCPC网络赛(部分题解)

1002 array(线段树)

题目链接

题意 :

给定一个 a i ai 数组,每个元素都不相同,且都在 1 n 1-n 的范围之内,即是一个排列,要求实现两种操作

第一种操作是: ( 1 , p o s ) (1,pos) ,把 a p o s a_{pos} 的的值加上 1 e 7 1e7

第二种操作是: ( 2 , r , k ) (2,r,k) ,查询 大于等于 k k 的且不与 [ 1 , r ] [1,r] 之间元素值相同的最小数。

思路:

我们发现 n < = 1 e 5 n<=1e5 而第一个操作是加上 1 e 7 1e7 那么就相当于将 a p o s a_{pos} 从集合中去掉。我们考虑用线段树维护可以使用 i i 的最大 r r ,线段树下标维护 i i ,值维护 r r 。那么每一次查询就是在线段树中查询 [ k , n ] [k,n] 区间中值大于等于 r r 的下标,使用线段树上二分即可。

代码:

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e5+10;
int a[N];
struct node{
    int l,r,mx;
}e[N*4];
void up(int x){
    e[x].mx=max(e[ls].mx,e[rs].mx);
}
void built(int x,int l,int r){
    e[x].l=l;e[x].r=r;
    if(l==r){
        e[x].mx=0;
        return ;
    }
    int mid=(l+r)/2;
    built(ls,l,mid);built(rs,mid+1,r);up(x);
}
void add(int x,int pos,int v){
    if(e[x].l==e[x].r){
        e[x].mx=v;return ;
    }
    int mid=(e[x].l+e[x].r)/2;
    if(pos<=mid)add(ls,pos,v);
    else add(rs,pos,v);
    up(x);
}
int query11(int x,int k){//二分
    if(e[x].l==e[x].r)return e[x].l;
    if(e[ls].mx>=k)return query11(ls,k);
    else return query11(rs,k);
}
int query1(int x,int LL,int RR,int k){//划分区间
    if(LL>RR)return -1;
    if(e[x].l>RR||e[x].r<LL)return -1;
    if(e[x].l>=LL&&e[x].r<=RR){
        if(e[x].mx>=k)return query11(x,k);
        else return -1;
    }
    int ans=query1(ls,LL,RR,k);
    if(ans==-1)ans=query1(rs,LL,RR,k);
    return ans;

}
int n,m,T;
int main()
{
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        built(1,1,n+1);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);add(1,a[i],i-1);
        }
        add(1,n+1,n+1);
        int op,x,y,last=0;
        for(int i=1;i<=m;i++){
            scanf("%d",&op);
            if(op==1){
                scanf("%d",&x);x^=last;
                add(1,a[x],n+1);//赋值成n+1
            }else{
                scanf("%d%d",&x,&y);
                x^=last,y^=last;
                int ans=query1(1,y,n+1,x);
                //cout<<endl;
                printf("%d\n",ans);
               //cout<<endl;
                last=ans;//强制在线
            }
        }
    }
}

1003 K-th occurrence (后缀数组 + 主席树 + 二分)

题意:

就是给一个字符串 s s s &lt; = 1 e 5 |s|&lt;=1e5 ,有 q q 次询问,每次询问 ( l , r , k ) (l,r,k) 表示子串 s l , s l + 1 . . . s r s_l,s_{l+1}...s_r k k 次出现的位置。

思路:

据说是套路题,但是比赛的时候不会后缀数组,我们先将样例在纸上求出 s a [ i ] , h e g h t [ i ] , r k [ i ] sa[i],heght[i],rk[i] 然后对于一个 ( l , r ) (l,r) 可以二分出在 h e g h t [ i ] heght[i] 的区间,对应到 s a [ i ] sa[i] 数组中的第 k k 大值即可。

要先预处理出 s a [ i ] sa[i]的主席树 h e g h t [ i ] S T heght[i]的ST表

代码:

下面的代码会WA,调试了一个晚上没成功,不过大体思路是对的

#include<bits/stdc++.h>
#define ls x<<1
#define rs x<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=2e5+10;
int TT;
char S[N];
int s[N];
int n,q;
struct _SA{
    int sa[N],t1[N],t2[N],c[N],rk[N],ht[N];
    /*
    void build(int s[],int n,int m){
        int i,j,p,*x=t1,*y=t2;
        for(i=0;i<m;i++)c[i]=0;
        for(i=0;i<n;i++)c[x[i]=s[i]]++;
        for(i=1;i<m;i++)c[i]+=c[i-1];
        for(i=n-1;i>=0;i--)sa[--c[x[i]]]=i;
        for(j=1;j<=n;j<<=1){
            p=0;
            for(i=n-j;i<n;i++)y[p++]=i;
            for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
            for(i=0;i<m;i++)c[i]=0;
            for(i=0;i<n;i++)c[x[y[i]]]++;
            for(i=1;i<m;i++)c[i]+=c[i-1];
            for(i=n-1;i>=0;i--)sa[--c[x[y[i]]]]=y[i];
            swap(x,y);
            p=1;x[sa[0]]=0;
            for(i=1;i<m;i++)
            x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j]?p-1:p++;
            if(p>=n)break;
            m=p;
        }
    }
    */
    void init(){
        for(int i=1;i<=n;i++)sa[i]=ht[i]=rk[i]=t1[i]=t2[i]=c[i]=0;
    }
    void build(int s[],int n,int m)
    {
        int i,j,p,*x=t1,*y=t2;
        for(i=1;i<=m;i++)c[i]=0;
        for(i=1;i<=n;i++)c[x[i]=s[i]]++;
        for(i=2;i<=m;i++)c[i]+=c[i-1];
        for(i=n;i>=1;i--)sa[c[x[i]]--]=i;
        for(j=1;j<=n;j<<=1)
        {
            p=0;
            for(i=n-j+1;i<=n;i++)y[++p]=i;
            for(i=1;i<=n;i++)if(sa[i]>j)y[++p]=sa[i]-j;
            for(i=1;i<=m;i++)c[i]=0;
            for(i=1;i<=n;i++)c[x[i]]++;
            for(i=2;i<=m;i++)c[i]+=c[i-1];
            for(i=n;i>=1;i--)sa[c[x[y[i]]]--]=y[i],y[i]=0;
            swap(x,y);
            p=1;x[sa[1]]=1;
            for(i=2;i<=n;i++)
                x[sa[i]]=(y[sa[i-1]]==y[sa[i]] && y[sa[i-1]+j]==y[sa[i]+j])?p:++p;
            if(p==n)break;
            m=p;
        }
    }
    void getht(int s[],int n)
    {
        int i,j,k=0;
        for(i=1;i<=n;i++)rk[sa[i]]=i;
        for(i=1;i<=n;i++)
        {
            if(rk[i]==1)continue;
            if(k)k--;
            j=sa[rk[i]-1];
            while(j+k<=n&&i+k<=n&&s[i+k]==s[j+k])k++;
            ht[rk[i]]=k;
        }
    }
}SA;
/*
struct Tree{//一开始想用线段树维护最小值,果断T了
    int l[N*4],r[N*4],mi[N*4];
    void up(int x){
        mi[x]=min(mi[ls],mi[rs]);
    }
    void built(int x,int L,int R){
        l[x]=L;r[x]=R;
        if(L==R){
            mi[x]=SA.ht[L];
            return ;
        }
        int mid=(L+R)/2;
        built(ls,L,mid);built(rs,mid+1,R);
        up(x);
    }
    int query(int x,int LL,int RR){
        if(LL<=l[x]&&RR>=r[x]){
            return mi[x];
        }
        int mid=(l[x]+r[x])/2;
        int ans=inf;
        if(LL<=mid)ans=min(ans,query(ls,LL,RR));
        if(RR>mid)ans=min(ans,query(rs,LL,RR));
        return ans;
    }
}ST;
*/
int arr[N];
struct _ST{
    int lg[N];int st[N][21];
    void init(int n){
        for(int i=1;i<=n;i++)
        for(int j=0;j<=20;j++)st[i][j]=0;
        lg[0]=-1;for(int i=1;i<=n;i++)lg[i]=lg[i>>1]+1;
        for(int i=1;i<=n;i++)arr[i]=SA.ht[i];
        for(int i=1;i<=n;i++)st[i][0]=i;
        for(int i=1;i<=20;i++){
            for(int j=1;j<=(n+1-(1<<i));j++){
                st[j][i]=arr[st[j][i-1]]<=arr[st[j+(1<<(i-1))][i-1]]?st[j][i-1]:st[j+(1<<(i-1))][i-1];
            }
        }
    }
    /*
    int query(int L,int R){
        int k=lg[R-L+1];
        int id=arr[st[L][k]]<=arr[st[R-(1<<k)+1][k]]?st[L][k]:st[R-(1<<k)+1][k];
        return arr[id];
    }
    */
    int query(int i,int j) {
        if(i-1==j) return n-SA.sa[i-1]+1;
        int x=log2(j-i+1);
        return arr[min(st[i][x],st[j-(1<<x)+1][x])];
    }
}ST;
struct node{
    int l,r,sum;
}T[N*50];
int cnt=0;
int root[N];
void update(int l,int r,int &x,int y,int pos){
    T[++cnt]=T[y];T[cnt].sum++;x=cnt;
    if(l==r)return ;
    int mid=(l+r)/2;
    if(pos<=mid)update(l,mid,T[x].l,T[y].l,pos);
    else update(mid+1,r,T[x].r,T[y].r,pos);
}
int query(int l,int r,int x,int y,int k){
    if(l==r)return l;
    int mid=(l+r)/2;int sum=T[T[y].l].sum-T[T[x].l].sum;
    if(sum>=k)return query(l,mid,T[x].l,T[y].l,k);
    else return query(mid+1,r,T[x].r,T[y].r,k-sum);
}
int main()
{
    scanf("%d",&TT);
    while(TT--){
        cnt=0;
        scanf("%d%d",&n,&q);for(int i=0;i<=n+1;i++)root[i]=0;SA.init();
        for(int i=1;i<=n+1;i++)T[i].l=T[i].r=T[i].sum=0;//初始化
        scanf("%s",S+1);int mx=0;n=strlen(S+1);
        for(int i=1;i<=n;i++){
            s[i]=S[i]-'a'+1;
            mx=max(s[i],mx);
        }
        SA.build(s,n,mx);
        SA.getht(s,n);
        for(int i=1;i<=n;i++)update(1,n,root[i],root[i-1],SA.sa[i]);
        ST.init(n);
        for(int i=1,a,b,k;i<=q;i++){
            scanf("%d%d%d",&a,&b,&k);
            int len=(b-a+1);
            int x=SA.rk[a]; int L,R;
            int l=1,r=x;
            while(l<=r){
                int mid=(l+r)/2;
                if(ST.query(mid+1,x)>=len)r=mid-1,L=mid;
                else l=mid+1;
            }
            l=x,r=n;
            while(l<=r){
                int mid=(l+r)/2;
                if(ST.query(x+1,mid)>=len)l=mid+1,R=mid;
                else r=mid-1;
            }
            //L--;
            //cout<<L<<" "<<R<<endl;//找到能延伸的最大区间
            if(R-L+1<k){puts("-1");continue;}//出现次数小于k次就输出-1
            printf("%d\n",query(1,n,root[L-1],root[R],k));
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40400202/article/details/100077321