线段树、树状数组题目专题

来源:互联网 发布:逐浪cms oracle版 编辑:程序博客网 时间:2024/05/22 03:45

目录:
洛谷 P1531 I Hate It
洛谷 P1816 忠诚
洛谷 P1198 [JSOI2008]最大数
洛谷 P1972 [SDOI2009]HH的项链
洛谷 P2056 采花

洛谷 P1531 I Hate It

题目分析:
一道很裸的单点更新,单点查询,区间查询,直接上代码(线段树)

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>#define ls (rt<<1)#define rs (rt<<1|1) #define mid ((tree[rt].r+tree[rt].l)>>1)using namespace std;struct arr{    int max,l,r;}tree[1000000];int n,m;char s[2];inline int read(){    int x=0,w=1;char ch=getchar();    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();    if(ch=='-') w=-1,ch=getchar();    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();    return x*w; }inline void pushup(int rt) { tree[rt].max=max(tree[ls].max,tree[rs].max); }void build(int l,int r,int rt) {    tree[rt].l=l;tree[rt].r=r;    if(l==r) return ;    int midd=(l+r)>>1;    build(l,midd,ls);build(midd+1,r,rs);    pushup(rt);}void change(int l,int r,int c,int rt) {    if(tree[rt].l==l&&tree[rt].r==r) { tree[rt].max=c; return ; }    if(r<=mid) change(l,r,c,ls);    else if(l>mid) change(l,r,c,rs);        else{ change(l,mid,c,ls); change(mid+1,r,c,rs); }    pushup(rt);}int query(int l,int r,int rt) {    if(tree[rt].l==l&&tree[rt].r==r) { return tree[rt].max; }    if(r<=mid) return query(l,r,ls);    else if(l>mid) return query(l,r,rs);        else{ return max(query(l,mid,ls),query(mid+1,r,rs)); }    pushup(rt);}int QUERY(int l,int r,int rt) {    if(tree[rt].l==l&&tree[rt].r==r) { return tree[rt].max; }    int ans=0;    if(r<=mid) ans+=QUERY(l,r,ls);    else if(l>mid) ans+=QUERY(l,r,rs);        else{ ans+=QUERY(l,mid,ls); ans+=QUERY(mid+1,r,rs); }    pushup(rt);    return ans;}int main(){    n=read();m=read();    build(1,n,1);    for(register int i=1;i<=n;++i) {int x=read();change(i,i,x,1);}    for(register int i=1;i<=m;++i) {        cin>>s;int l=read(),r=read();        if(s[0]=='Q') {            printf("%d\n",query(l,r,1));//区间查询最大值        }        if(s[0]=='U') {            int u=QUERY(l,l,1);//单点查询            if(u<r) change(l,l,r,1);//如果小于r,则更新        }    }    return 0;}

洛谷 P1816 忠诚

题目分析:区间查询最小值,当时为了练RMQ,所以就没打线段树了。

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<cmath>using namespace std;int n,m;int f[100500][22],a[100500];inline int read(){    int x=0,w=1;char ch=getchar();    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();    if(ch=='-') w=-1,ch=getchar();    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();    return x*w; }inline void st() {    for(register int j=1;(1<<j)<=n;++j)        for(register int i=1;i+(1<<j)-1<=n;++i)            f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);//记住这里是i+(1<<(j-1))不是i+(1<<(j-1))-1 }inline int rmq(int l,int r) {    int j=0;    while(1<<(j+1)<=r-l+1) ++j;    return min(f[l][j],f[r-(1<<j)+1][j]);//记住这里右边是r-(1<<j)+1 }int main(){    n=read();m=read();    memset(f,0x3f,sizeof(f));     for(register int i=1;i<=n;++i) a[i]=read(),f[i][0]=a[i];    st();    for(register int i=1;i<=m;++i) {        int l=read(),r=read();        printf("%d ",rmq(l,r));    }    return 0;}

洛谷 P1198 [JSOI2008]最大数

题目分析:用线段树做的话,就是动态加点(其实我是觉得这算不上动态加点,只是投机取巧了下)因为题目给m个询问,所以最多会用到m个格子,所以可以一开始的时候建一个1~m的线段树,然后每次要加的时候,将当前区间长度+1的位置更新值就好

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#define lson l,mid,rt<<1#define rson mid+1,r,rt<<1|1using namespace std;const int MAXN=200005;inline int read(){    int x=0,w=1;char ch=getchar();    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();    if(ch=='-') w=-1,ch=getchar();    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();    return x*w; }int MOD,m,t,ma[MAXN<<2],n;void pushup(int rt){ ma[rt]=max(ma[rt<<1],ma[rt<<1|1]);}void build(int l,int r,int rt){    if(l==r){ ma[rt]=-0x7fffffff; return; }    int mid=l+((r-l)>>1);    build(lson); build(rson);    pushup(rt);}void change(int add,int loc,int l,int r,int rt){    if(r==loc&&l==loc){ ma[rt]=add; return; }//如果找到当前位置,就可以更新值     int mid=l+((r-l)>>1);    if(loc<=mid) change(add,loc,lson);    else change(add,loc,rson);    pushup(rt);}int query(int L,int R,int l,int r,int rt){    if(L<=l&&r<=R){ return ma[rt]; }    int mid=l+((r-l)>>1);    int q=-0x7fffffff;    if(L<=mid){q=max(q,query(L,R,lson));}    if(mid<R) q=max(q,query(L,R,rson));    return q;}int main(){    m=read();MOD=read();//在此题中,要加进的数肯定是小于m的,所以我们可以投机取巧一下,看A操作     build(1,m,1);    for(int i=1;i<=m;i++){        char c;        scanf(" %c ",&c);        int k=read();        if(c=='A'){ n++;k=((long long)k+t)%MOD; change(k,n,1,m,1);        }else { t=query(n-k+1,n,1,m,1); printf("%d\n",t); }    }    return 0;}

单调栈做法

这道题还有另外一种解法,就是单调栈。因为每次都是查询当前长度的前k个,所以我们可以用单调栈记录单调递增的值和位置即可,具体看代码。

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int MAXN=200005;inline int read(){    int x=0,w=1;char ch=getchar();    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();    if(ch=='-') w=-1,ch=getchar();    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();    return x*w; }int m,MOD,t,stack[MAXN],head,num[MAXN],cnt;int main(){    m=read();MOD=read();    for(int i=1;i<=m;i++){        char c; scanf(" %c ",&c); int k=read();        //两个操作的k不一样,第一个操作的k是数值,第二个操作的k是位置        if(c=='A'){            k=(k+t)%MOD;            cnt++; while(stack[head]<=k&&head) head--;//弹栈,维护单调栈            stack[++head]=k; num[head]=cnt;//压栈         }else {            int l=1,r=head,mid;            k=cnt-k+1;            while(l<=r){                mid=(l+r)>>1;                if(num[mid]<k) l=mid+1;//找到最后一个小于k的值                else r=mid-1;            }            printf("%d\n",t=stack[l]);        }    }    return 0;}

洛谷 P1972 [SDOI2009]HH的项链

题目分析:这道题是统计区间有多少个不同的数。此题有多种解法(下面给出一种解法,不过本蒟只是意会但不可言传,所以看代码吧)本蒟还是试着讲一下吧,我们用一个数组记录当前这个数字,上一次出现的位置(在此称为前驱吧),每次更新的时候将前驱-1,当前这个数字+1。为什么呢?因为在前驱和当前这个数字,我们在统计个数的时候会将两个都统计进去(树状数组更新时),所以要把前驱-1,当前这个数字+1,最后输出的时候就很方便了。(如果要问为什么可以这样做的话,我就不知道了,可以上网搜博客)
code 1

#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#include <cmath>using namespace std;const int N=5e4+5,M=2e5+5,INF=1e6+5;int n,m,a[N],last[INF];struct data{    int l,r,id,ans;}q[M];int c[N],p[N];inline int read(){    int x=0,w=1;char ch=getchar();    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();    if(ch=='-') w=-1,ch=getchar();    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();    return x*w; }inline bool cmp(data a,data b){return a.r<b.r;}inline void add(int p,int v){ while(p<=n) {c[p]+=v;p+=p&-p;}}inline int sum(int p){ int res=0; while(p>0) {res+=c[p];p-=p&-p;}  return res;}int main(){    n=read(); for(register int i=1;i<=n;++i) a[i]=read();    m=read(); for(register int i=1;i<=m;++i) q[i].l=read(),q[i].r=read(),q[i].id=i;    sort(q+1,q+1+m,cmp);    for(register int i=1;i<=m;++i) p[q[i].id]=i;    int pos=1;    for(register int i=1;i<=n;++i){        if(last[a[i]]) add(last[a[i]],-1);        add(i,1); last[a[i]]=i;        while(q[pos].r==i) q[pos].ans=sum(q[pos].r)-sum(q[pos].l-1),pos++;//因为可能有多个相同的,所以都处理一次     }    for(register int i=1;i<=m;++i) printf("%d\n",q[p[i]].ans);    return 0;}

code 2

#include <cstdio>#include <algorithm>using namespace std;struct node{    int l, r, ans, i;    bool operator < (const node & A) const{        if(r != A.r) return r < A.r;        return l < A.l;    }}q[200007];int tree[250005], a[250005], f[250005], p[250005];int pre[250005], hav[2500005];void add(int i, int x){ if(!i) return; for(; i <= 200005; i += i&-i) tree[i] += x; }int query(int i){ int s = 0; for(; i; i -=i&-i) s += tree[i]; return s; }int main(){    int i, j, n, l, r, m;    scanf("%d", &n);    for(i = 1; i <= n; i++) scanf("%d", &a[i]);    for(i = 1; i <= n; i++){ pre[i] = hav[a[i]]; hav[a[i]] = i; }    scanf("%d", &m);    for(i = 1; i <= m; i++){ scanf("%d%d", &q[i].l, &q[i].r); q[i].i = i;}    sort(q + 1, q + i);    for(i = 1; i <= m; i++) p[q[i].i] = i;    j = 1;    for(i = 1; i <= m; i++){        while(j <= q[i].r) add(j, 1), add(pre[j], -1), j++;        q[i].ans = query(q[i].r) - query(q[i].l - 1);    }    for(i = 1; i <= m; i++) printf("%d\n", q[p[i]].ans);    return 0;}

洛谷 P2056 采花

题目分析:次题与上一道题累次,上一道题求的是有多少不同的数字,这一道题要求的是在一个区间内有多少种不同的数字满足个数大于等于2.因为上题记录了一个前驱,那在这里就记录前驱和前驱的前驱,其他操作像上一道题一样。

#include<iostream>#include<cstdio>#include<cstdlib>#include<algorithm>using namespace std;int t[100001],n,m,c,mp[100001],ne[100001],p[100001];struct gjh{    int l,r,id,ans;}q[100001];inline int read(){    int x=0,w=1;char ch=getchar();    while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();    if(ch=='-') w=-1,ch=getchar();    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();    return x*w; }inline void add(int x,int y){while(x<=n)t[x]+=y,x+=x&-x;}inline int sum(int x){int num=0;while(x>0)num+=t[x],x-=x&-x;return num;}inline bool cmp(gjh a,gjh b){return a.l==b.l?a.r<b.r:a.l<b.l;}int main(){    n=read();c=read();m=read();    for(register int i=1;i<=n;i++)  mp[i]=read();    for(register int i=n;i>0;i--)   ne[i]=p[mp[i]],p[mp[i]]=i;    for(register int i=1;i<=c;i++)  if(p[i]&&ne[p[i]])add(ne[p[i]],1);    for(register int i=1;i<=m;i++)  q[i].l=read(),q[i].r=read(),q[i].id=i;    sort(q+1,q+m+1,cmp);//以左端点为第一关键字右端点为第二关键字     for(register int i=1;i<=m;++i) p[q[i].id]=i;    int l=1;    for(register int i=1;i<=m;i++){        while(l<q[i].l){            if(ne[l])add(ne[l],-1);            if(ne[l]&&ne[ne[l]])add(ne[ne[l]],1);            ++l;         }        q[i].ans=sum(q[i].r)-sum(q[i].l-1);    }    for(int i=1;i<=m;i++)printf("%d\n",q[p[i]].ans);    return 0;}

以上代码有部分不是自己手打,因为考试临近,主要是为了体验思想

原创粉丝点击