线段树专题

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\n",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;
}

posted @ 2020-10-22 11:54  yzhx  阅读(97)  评论(0编辑  收藏  举报