线段树维护区间字符的两道例题(CF240F CF558E)

CF240F:https://www.luogu.com.cn/problem/CF240F

题目大意:给定一个长为n的由a到z组成的字符串,有m次操作,每次操作将[l,r]的字符串进行重排,得到字典序最小的字符串,输出m次操作后的字符串。

大致思路:1.首先我们要想区间内的字典序最小的回文串要怎么构造。 回文串无非就两种类型:有一个字母出现奇数次其余字母出现偶数次和所有字母都出现偶数次两种类型。那么当给出一段区间的时候我们要对这两类进行分类讨论,那么自然而然我们需要一种方法去快速的维护和查询区间内每个字母的数量。因为要对区间进行重组,所以区间内字符串原本是以什么样的顺序排列对重组过程其实没有一点影响。因此我们可以开26颗线段树,去维护每个字母。

2.在分类讨论的过程中,遇到两个及以上的字母出现奇数次那么就是无法操作,直接跳过即可。如果遇到一个出现奇数次的字符,那么回文串的中心位置一定是他,我们先填上即可。接着我们从大到小或者从小到大遍历26个字母都行,如果说在区间内存在这种字母那么以对称的方式在两边填充即可,这样能保证得到的回文串一定是字典序最小的。如果遇到所有字母都出现偶数次的情况那么直接填充,那么只要跳过中心填充出现奇数次的字母那一步即可。

3.至于填充的具体操作。可以先把原先的位置全部都初始化,然后再每次根据当前遍历到的字母出现次数固定一个长度区间,在左右两边进行填充即可。

代码:

#define maxn 100010
int n,m;
char s[maxn];
struct node{
    int l,r;
    int sum,tag;
}seg[26][maxn<<2];
void pushup(int c,int rt)
{
    seg[c][rt].sum=seg[c][lson].sum+seg[c][rson].sum;
}
void build(int c,int rt,int l,int r)
{
    seg[c][rt].l=l;
    seg[c][rt].r=r;
    seg[c][rt].tag=-1;
    if(l==r)
    {
        seg[c][rt].sum=((s[l]-'a')==c);
        return ;
    }
    int mid = (l+r)>>1;
    build(c,lson,l,mid);
    build(c,rson,mid+1,r);
    pushup(c,rt);
}
void pushdown(int c,int rt)
{
    if(seg[c][rt].tag!=-1)
    {
        seg[c][lson].sum=(seg[c][lson].r-seg[c][lson].l+1)*seg[c][rt].tag;
        seg[c][rson].sum=(seg[c][rson].r-seg[c][rson].l+1)*seg[c][rt].tag;
        seg[c][lson].tag=seg[c][rt].tag;
        seg[c][rson].tag=seg[c][rt].tag;
        seg[c][rt].tag=-1;
    }
}
void modify(int c,int rt,int x,int y,int val)
{
    int l=seg[c][rt].l;
    int r=seg[c][rt].r;
    if(x<=l&&r<=y)
    {
        seg[c][rt].sum=(r-l+1)*val;
        seg[c][rt].tag=val;
        return ;
    }
    int mid = (l+r)>>1;
    pushdown(c,rt);
    if(x<=mid) modify(c,lson,x,y,val);
    if(y>mid) modify(c,rson,x,y,val);
    pushup(c,rt);
}
int query(int c,int rt,int x,int y)
{
    int l=seg[c][rt].l;
    int r=seg[c][rt].r;
    if(x<=l&&r<=y)
    {
        return seg[c][rt].sum;
    }
    int mid = (l+r)>>1;
    pushdown(c,rt);
    int ans=0;
    if(x<=mid) ans+=query(c,lson,x,y);
    if(y>mid) ans+=query(c,rson,x,y);
    return ans;
}
int main()
{
    #ifdef ONLINE_JUDGE
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
    #endif
    // ios::sync_with_stdio(false);
    // cin.tie(0);
    // cout.tie(0);
    scanf("%d%d%s",&n,&m,s+1);
    for(int i=0;i<26;i++)
    {
        build(i,1,1,n);
    }
    while(m--)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        int a[26];
        int odd=0;
        int le=-1;
        for(int i=0;i<26;i++)
        {
            a[i]=query(i,1,l,r);
            if(a[i]&1)
            {
                ++odd;
                le=i;
            }
        }
        if(odd>1) continue;
        for(int i=0;i<26;i++)
        {
            modify(i,1,l,r,0);
        }
        if(odd)
        {
            --a[le];
            modify(le,1,(l+r)/2,(l+r)/2,1);
        }
        int nl=l,nr=r;
        for(int i=0;i<26;i++)
        {
            if(a[i])
            {
                modify(i,1,nl,nl+a[i]/2-1,1);
                nl+=a[i]/2;
                modify(i,1,nr-a[i]/2+1,nr,1);
                nr-=a[i]/2;
            }
        }
    }
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<26;j++)
        {
            if(query(j,1,i,i))
            {
                printf("%c",j+'a');
                break;
            }
        }
    }
    return 0;
}

CF558E :https://www.luogu.com.cn/problem/CF558E

题目大意:每次询问给定一个区间和排序种类(升序或降序),最后输出所有操作结束后的字符串

思路:和上一题的思路差不多,用26颗线段树维护字符串,然后再根据排序需求从小到大或者从大到小遍历字符串进行填充。不同的是这道题不需要对称和分出现次数奇偶的情况,比上一题简单一些。

#define maxn 200010
int n,m;
char s[maxn];
struct node{
    int l,r;
    int sum,tag;
}seg[26][maxn<<2];
void pushup(int c,int rt)
{
    seg[c][rt].sum=seg[c][lson].sum+seg[c][rson].sum;
}
void build(int c,int rt,int l,int r)
{
    seg[c][rt].l=l;
    seg[c][rt].r=r;
    seg[c][rt].tag=-1;
    if(l==r)
    {
        seg[c][rt].sum=((s[l]-'a')==c);
        return ;
    }
    int mid = (l+r)>>1;
    build(c,lson,l,mid);
    build(c,rson,mid+1,r);
    pushup(c,rt);
}
void pushdown(int c,int rt)
{
    if(seg[c][rt].tag!=-1)
    {
        seg[c][lson].sum=(seg[c][lson].r-seg[c][lson].l+1)*seg[c][rt].tag;
        seg[c][rson].sum=(seg[c][rson].r-seg[c][rson].l+1)*seg[c][rt].tag;
        seg[c][lson].tag=seg[c][rt].tag;
        seg[c][rson].tag=seg[c][rt].tag;
        seg[c][rt].tag=-1;
    }
}
void modify(int c,int rt,int x,int y,int val)
{
    int l=seg[c][rt].l;
    int r=seg[c][rt].r;
    if(x<=l&&r<=y)
    {
        seg[c][rt].sum=(r-l+1)*val;
        seg[c][rt].tag=val;
        return ;
    }
    int mid = (l+r)>>1;
    pushdown(c,rt);
    if(x<=mid) modify(c,lson,x,y,val);
    if(y>mid) modify(c,rson,x,y,val);
    pushup(c,rt);
}
int query(int c,int rt,int x,int y)
{
    int l=seg[c][rt].l;
    int r=seg[c][rt].r;
    if(x<=l&&r<=y)
    {
        return seg[c][rt].sum;
    }
    int mid = (l+r)>>1;
    pushdown(c,rt);
    int ans=0;
    if(x<=mid) ans+=query(c,lson,x,y);
    if(y>mid) ans+=query(c,rson,x,y);
    return ans;
}
int main()
{
    // ios::sync_with_stdio(false);
    // cin.tie(0);
    // cout.tie(0);
    scanf("%d%d%s",&n,&m,s+1);
    for(int i=0;i<26;i++)
    {
        build(i,1,1,n);
    }
    while(m--)
    {
        int l,r,op;
        scanf("%d%d%d",&l,&r,&op);
        int a[26];
        for(int i=0;i<26;i++)
        {
            a[i]=query(i,1,l,r);
        }
        for(int i=0;i<26;i++)
        {
            modify(i,1,l,r,0);
        }
        int nl=l,nr=r;
        if(op==1)
        {
            for(int i=0;i<26;i++)
            {
                modify(i,1,nl,nl+a[i]-1,1);
                nl+=a[i];
            }
        }
        else 
        {
            for(int i=25;i>=0;i--)
            {
                modify(i,1,nl,nl+a[i]-1,1);
                nl+=a[i];
            }
        }
    }
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<26;j++)
        {
            if(query(j,1,i,i))
            {
                printf("%c",j+'a');
                break;
            }
        }
    }
    return 0;
}
posted @ 2024-05-25 15:06  Captainfly19  阅读(3)  评论(0编辑  收藏  举报