• 线段树专题


    感谢卡老师上课分享的题单

    CF558E

    题意

    Luogu
    给定一个长度不超过10^5的字符串(小写英文字母),和不超过50000个操作。
    每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序。
    输出最终的字符串。

    题解

    因为字符集只有26,所以区间内重复元素个数很多,排序后相同元素会聚拢,即区间覆盖
    考虑用线段树记录区间内每个字母出现的次数,然后按序区间覆盖即可

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define re register
    #define ll long long
    #define get getchar()
    #define in inline
    in int read()
    {
        int t=0, x=1; char ch=get;
        while((ch<'0' || ch>'9') && ch!='-')ch=get;
        if(ch=='-') ch=get, x=-1;
        while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
        return x*t;
    }
    const int _=1e5+5;
    int n,m;
    char s[_];
    int t[_<<3][27],la[_<<3],ans[27];
    in void add(int k,int l,int r,int x,int y)
    {
        if(l==r) {
            t[k][y]++;
            return;
        }
        int mid=l+r>>1;
        if(x<=mid) add(k<<1, l, mid, x, y);
        else add(k<<1|1, mid+1, r, x, y);
        t[k][y]=t[k<<1][y]+t[k<<1|1][y];
    }
    in void pushdown(int k,int l,int r)
    {
        if(la[k]==0) return ;
        la[k<<1]=la[k<<1|1]=la[k];
        for(re int i=1;i<=26;i++) t[k<<1][i]=t[k<<1|1][i]=0;
        int mid=l+r>>1;
        t[k<<1][la[k]]=mid-l+1;
        t[k<<1|1][la[k]]=r-mid;
        la[k]=0;
    }
    in void query(int k,int l,int r,int x,int y)
    {
        if(x<=l && r<=y)
        {
            for(re int i=1;i<=26;i++) ans[i]+=t[k][i];
            return;
        }
        int mid=l+r>>1;
        pushdown(k,l,r);
        if(x<=mid) query(k<<1, l, mid, x, y);
        if(y>mid) query(k<<1|1, mid+1, r, x, y);
    }
    in void update(int k,int l,int r,int x,int y,int z)
    {
        if(x<=l && r<=y) {
            for(re int i=1;i<=26;i++) t[k][i]=0;
            t[k][z]=r-l+1;
            la[k]=z;
            return;
        }
        pushdown(k,l,r);
        int mid=l+r>>1;
        if(x<=mid) update(k<<1, l, mid, x, y, z);
        if(y>mid) update(k<<1|1, mid+1, r, x, y, z);
        for(re int i=1;i<=26;i++) t[k][i]=t[k<<1][i]+t[k<<1|1][i];
    }
    int main()
    {
        n=read(), m=read();
        scanf("%s",s+1);
        for(re int i=1;i<=n;i++) add(1,1,n,i,s[i]-'a'+1);
        for(re int i=1;i<=m;i++)
        {
            int l=read(), r=read(), z=read();
            query(1,1,n,l,r);
            int p=l;
            if(z==1)
                for(re int j=1;j<=26;j++)
                {
                    if(ans[j]==0) continue;
                    update(1,1,n,p,p+ans[j]-1,j);
                    p+=ans[j];
                }
            else
                for(re int j=26;j>=1;j--)
                {
                    if(ans[j]==0) continue;
                    update(1,1,n,p,p+ans[j]-1,j);
                    p+=ans[j];
                }
            memset(ans,0,sizeof(ans));
        }
        for(re int i=1;i<=n;i++)
        {
            query(1,1,n,i,i);
            int k=0;
            for(re int j=1;j<=26;j++) if(ans[j]) {k=j;break;}
            //极其愚蠢的输出方式:查询每个点的字符集,找那个字符有值/kk
            ans[k]=0;
            printf("%c",char(k-1+'a')); 
        }
        return 0;
    }
    
    

    CF438D

    题意

    Luogu
    给定数列,区间查询和,区间取模,单点修改。

    题解

    老套路了
    因为取模次数有限,最多log次
    考虑暴力修改,并记录区间最大值,当最大值小于模数时直接跳过

    #include<bits/stdc++.h>
    using namespace std;
    #define re register
    #define ll long long
    #define get getchar()
    #define in inline
    in int read()
    {
        int t=0, x=1; char ch=get;
        while((ch<'0' || ch>'9') && ch!='-') ch=get;
        if(ch=='-') ch=get, x=-1;
        while(ch<='9'  && ch>='0') t=t*10+ch-'0', ch=get;
        return t*x;
    }
    const int _=2e5+1;
    int n,m,mx[_<<4];
    ll sum[_<<4];
    in void pushup(int k)
    {
        sum[k]=sum[k<<1]+sum[k<<1|1];
        mx[k]=max(mx[k<<1],mx[k<<1|1]);
    }
    in void add(int k,int l,int r,int x,int y)
    {
        if(l==r)
        {
            mx[k]=sum[k]=y;
            return ;
        }
        int mid=l+r>>1;
        if(x<=mid) add(k<<1, l, mid, x, y);
        else add(k<<1|1, mid+1, r, x, y);
        pushup(k);
    }
    in void mod(int k,int l,int r,int x,int y,int p)
    {
        if(mx[k]<p) return; //区间最大值小于模数
        if(l==r)
        {
            mx[k]=sum[k]%=p;
            return;
        }
        int mid= l+r>>1;
        if(x<=mid) mod(k<<1, l, mid, x, y, p);
        if(y>mid) mod(k<<1|1, mid+1, r, x, y, p);
        pushup(k);
    }
    in ll query(int k,int l,int r,int x,int y)
    {
        if(x<=l && r<=y) return sum[k];
        int mid=l+r>>1;
        ll s=0;
        if(x<=mid) s+=query(k<<1, l, mid, x, y);
        if(y>mid) s+=query(k<<1|1, mid+1, r, x, y);
        return s;
    }
    int main()
    {
        n=read(), m=read();
        for(re int i=1;i<=n;i++) add(1,1,n,i,read());
        for(re int i=1;i<=m;i++)
        {
            int opt=read();
            if(opt==1) {
                int l=read(), r=read();
                printf("%lld
    ",query(1,1,n,l,r));
            }
            if(opt==2) {
                int l=read(), r=read(), p=read();
                mod(1,1,n,l,r,p);
            }
            if(opt==3) {
                int x=read(), y=read();
                add(1,1,n,x,y);
            }
        }
        return 0;
    }
    

    CF914D

    题意

    luogu
    给定一个长为n的序列,m次操作
    操作1: 询问一个区间可否在最多修改一个数后区间gcd=x
    操作2: 单点修改

    题解

    区间gcd=x,不难发现若是区间gcd%x=0则可通过修改一个数符合题意,
    所以维护区间gcd,每次查询区间中mod x不为0的个数,若大于1,则非法

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define re register
    #define ll long long
    #define get getchar()
    #define in inline
    in int read()
    {
        int t=0, x=1; char ch=get;
        while((ch<'0' || ch>'9') && ch!='-')ch=get;
        if(ch=='-' ) ch=get, x=-1;
        while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
        return t*x;
    }
    const int _=5e5+1;
    int n,m,g[_<<4];
    in int gcd(int x,int y)
    {
        if(x==-1 || y==-1) return max(x,y);
        re int t;
        while(y!=0)
            t=x, x=y,y=t%y;
        return x;
    }
    in void add(int k,int l,int r,int x,int y)
    {
        if(l==r) {g[k]=y; return;}
        int mid=l+r>>1;
        if(x<=mid) add(k<<1, l, mid, x, y);
        else add(k<<1|1, mid+1, r, x, y);
        g[k]=gcd(g[k<<1|1],g[k<<1]);
    }
    int cnt; //记录%p不为0的个数
    in void query(int k,int l,int r,int x,int y,int p)
    {
        if(g[k]%p==0) return;
        if(cnt>1) return; //大于1则无解,直接退出
        if(l==r)
        {
            if(g[k]%p>0) cnt++;
            return;
        }
        int mid=l+r>>1;
        if(x<=mid) query(k<<1, l, mid, x, y, p);
        if(y>mid) query(k<<1|1, mid+1, r, x, y, p);
    }
    int main()
    {
        memset(g,-1,sizeof(g));
        n=read();
        for(re int i=1; i<=n; i++) add(1,1,n,i,read());
        m=read();
        for(re int i=1;i<=m;i++)
        {
            int type=read(),l=read(),r=read();
            if(type==1)
            {
                int x=read();
                cnt=0;
                query(1,1,n,l,r,x);
                if(cnt<=1) puts("YES");
                else puts("NO");
            }
            else add(1,1,n,l,r);
        }
        return 0;
    }      
    

    CF920F

    题意

    洛谷
    给定 n 个数的数组 a,m 次操作。操作有两种:
    操作1: 将给定区间中的数都变为他们的约数个数
    操作2: 查询区间和

    题解

    和之前CF438D的思路很像,也都是老套路了.
    不难发现大部分数经过几次操作1后都会变成1或者2
    然后就是老套路了:
    维护区间和,并记录标记当前区间是否都为1,2

    [TJOI2016]排序

    题意

    Luogu
    给定一个1~n的排列,m次操作,每次操作将某一区间升序或降序排序,求最后位置p上的数

    题解

    此题思路很妙,值得借鉴

    考虑我们之前 CF558E 这题,为啥能直接做,而这里不行呢?

    有没有办法把那题的做法搬运到这里来呢?

    自然是有的

    考虑这题只要求某个位置的值,所以我们实际上是可以"不求甚解"的,
    不需要关心其他位置的值,所以相对于该点而言,其他位置上的值与它的关系只有大于或是小于

    考虑二分答案

    直接二分p位置最后的值mid,将大于等于它的数全当成1,小于的数全当成0,然后按CF558E的做法跑
    若最后该点值不为1,则说明大于等于mid的值是不可能作为答案的,否则反之

    代码

    #include<bits/stdc++.h>
    using namespace std;
    #define re register
    #define ll long long
    #define get getchar()
    #define in inline
    in int read()
    {
        int t=0; char ch=get;
        while(ch<'0' || ch>'9') ch=get;
        while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
        return t;
    }
    const int _=2e5+4;
    struct question{
        int l,r,opt;
    }q[_];
    int n,m,sum[_<<3][2],Q,a[_],lazy[_<<3],b[_];
    in void pushup(int k) {  sum[k][0]=sum[k<<1][0]+sum[k<<1|1][0], sum[k][1]=sum[k<<1][1]+sum[k<<1|1][1]; }
    in void build(int k,int l,int r,int lim)
    {
        lazy[k]=-1; 
        if(l==r) {
            if(a[l]>=lim) sum[k][1]=1,sum[k][0]=0;
            else sum[k][0]=1,sum[k][1]=0;
            return;
        }
        int mid=l+r>>1;
        build(k<<1,l,mid,lim),build(k<<1|1,mid+1,r,lim);
        pushup(k);
    }
    in void pushdown(int k,int l,int r) {
        if(lazy[k]==-1) return;
        int qwe=lazy[k],mid=l+r>>1;
        lazy[k]=-1;
        lazy[k<<1]=lazy[k<<1|1]=qwe;
        sum[k<<1][qwe]=mid-l+1;
        sum[k<<1|1][qwe]=r-mid;
        sum[k<<1][qwe^1]=sum[k<<1|1][qwe^1]=0;
    }
    in void update(int k,int l,int r,int x,int y,int z)
    {
        if(x>y) return;
        if(x<=l && r<=y) {sum[k][z]=r-l+1,sum[k][z^1]=0,lazy[k]=z; return ;}
        pushdown(k,l,r);
        int mid=l+r>>1;
        if(x<=mid) update(k<<1,l,mid,x,y,z);
        if(y>mid) update(k<<1|1,mid+1,r,x,y,z);
        pushup(k);
    }
    in int query(int k,int l,int r,int x,int y,int z)
    {
        if(x<=l && r<=y) return sum[k][z];
        pushdown(k,l,r);
        int mid=l+r>>1,s=0;
        if(x<=mid) s+=query(k<<1,l,mid,x,y,z);
        if(y>mid) s+=query(k<<1|1,mid+1,r,x,y,z);
        return s;
    }
    in bool check(int k)
    {
        build(1,1,n,k);
        for(re int i=1;i<=m;i++)
        {
            if(q[i].opt==1) { //按先1,再0进行区间覆盖,即降序排序
                int cnt=query(1,1,n,q[i].l,q[i].r,1);
                update(1,1,n,q[i].l,q[i].l+cnt-1,1); 
                update(1,1,n,q[i].l+cnt,q[i].r,0); 
            }
            else { //升序
                int cnt=query(1,1,n,q[i].l,q[i].r,0);
                update(1,1,n,q[i].l,q[i].l+cnt-1,0);
                update(1,1,n,q[i].l+cnt,q[i].r,1);
            }
        }
        
        if(query(1,1,n,Q,Q,1)) return 1;
        return 0;
    }
    int main()
    {
        n=read(),m=read();
        for(re int i=1;i<=n;i++) a[i]=read();
        for(re int i=1;i<=m;i++) q[i].opt=read(),q[i].l=read(),q[i].r=read();
        Q=read();
        int l=1,r=n,ans=0x3f3f3f3f;
        while(l<=r) {
            int mid=l+r>>1;
            if(check(mid)) ans=mid, l=mid+1;
            else r=mid-1;
        }
        cout<<ans<<endl;
    }
    
    
  • 相关阅读:
    获取随机数
    性能测试工具
    Oracle 级联删除
    一些shell用法
    英文
    主题:【元宵赏灯】蛇年杭州元宵赏灯攻略(上城区、滨江区、下城区)
    CListCtrl 列表选中项非焦点时也是藍色
    ASCII码表
    杭州市公积金提取及相关知识
    ListBox设置水平滚动条
  • 原文地址:https://www.cnblogs.com/yzhx/p/13857422.html
Copyright © 2020-2023  润新知