• 【日记】12.2


    12.2日记

    今日主要复习&学习了一些基本的数据结构:树状数组,线段树,单调队列,单调栈,并做了一些简单例题。

    树状数组

    确实短小精悍。

    能做的事情:

    • 单点修改+区间查询和(BIT)
    • 区间加减+单点查询和(BIT2)维护差分数列
    • 区间加减+区间查询和(BIT3)维护差分数列+D[i]*(i-1)

    模板题:

    1. 洛谷P3374:单点加减+区间求和
    //单点修改+区间查询和
    #include<bits/stdc++.h>
    using namespace std;
    const int M=5e5+20;
    int num_bit,a[M],c[M];
    inline int lowbit(int x){return x&(-x);}
    inline void update(int x,int k){
        while(x<=num_bit)
            c[x]+=k,x+=lowbit(x);
    }
    inline int query(int x){//1-x的和
        int ans=0;
        while(x)
            ans+=c[x],x-=lowbit(x);
        return ans;
    }
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        num_bit=n;
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]),update(i,a[i]);
        for(int i=1;i<=m;++i){
            int op,y,z;
            scanf("%d%d%d",&op,&y,&z);
            if (op==1)
                update(y,z);
            else
                printf("%d
    ",query(z)-query(y-1));
        }
        return 0;
    }
    
    1. P3368:区间加减+单点询问
    //区间修改+单点查询和
    #include<bits/stdc++.h>
    using namespace std;
    const int M=1e6+20;
    int num_bit,a[M],d[M];
    int sum1[M];
    inline int lowbit(int x){return x&(-x);}
    inline void update(int i,int k){
        while(i<=num_bit)
            d[i]+=k,i+=lowbit(i);
    }
    inline int query(int i){//求1-i的D和,即a[i]的值
        int ans=0;
        while(i)
            ans+=d[i],i-=lowbit(i);
        return ans;
    }
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        num_bit=n;
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]),update(i,a[i]-a[i-1]);
        for(int i=1;i<=m;++i){
            int op,x,y,k;
            scanf("%d",&op);
            if (op==1)
                scanf("%d%d%d",&x,&y,&k),update(x,k),update(y+1,-k);
            else
                scanf("%d",&x),printf("%d
    ",query(x));
        }
        return 0;
    }
    
    1. POJ3468:区间加减+区间求和
    //区间修改+区间查询和
    #include<iostream>
    #include<cstdio>
    using namespace std;
    #define LL long long
    const int M=1e6+20;
    int num_bit,a[M];
    LL di[M],d[M];
    inline int lowbit(int x){return x&(-x);}
    inline void update(int i,LL k){
        int x=i;
        while(i<=num_bit)
            d[i]+=k,di[i]+=k*(x-1),i+=lowbit(i);
    }
    inline LL query(int i){//求1-i的a和
        LL ans=0,x=i;
        while(i)
            ans+=1LL*x*d[i]-di[i],i-=lowbit(i);
        return ans;
    }
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        num_bit=n;
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]),update(i,a[i]-a[i-1]);
        for(int i=1;i<=m;++i){
            int x,y,k;
            getchar();
            char op=getchar();
            if (op=='C')
                scanf("%d%d%d",&x,&y,&k),update(x,k),update(y+1,-k);
            else
                scanf("%d%d",&x,&y),printf("%lld
    ",query(y)-query(x-1));
        }
        return 0;
    }
    

    一道例题:

    1. 洛谷P1972。项链题,离线询问区间不同数个数。

    构造:右端点从小到大排序。每个数最后一次出现的位置记为1,其余为0。则区间和即为不同数个数。

    实现:设置last数组,last[i]表示i上一次出现的位置。这样输入一个数就先删掉上一个,再把这一个变成1。

    注意:离线处理,排序时要用结构体,多加一个输入编号。依次处理的时候只要直接赋值ans[输入编号],最后依次输出即可。

    //洛谷,区间不同数个数,离线
    #include<bits/stdc++.h>
    using namespace std;
    const int M=1e6+20;
    int num_bit,a[M],c[M],last[M],ans[M];
    inline int lowbit(int x){return x&(-x);}
    inline void update(int x,int k){
        while(x<=num_bit)
            c[x]+=k,x+=lowbit(x);
    }
    inline int query(int x){//1-x的和
        int ans=0;
        while(x)
            ans+=c[x],x-=lowbit(x);
        return ans;
    }
    struct Seg{
        int l,r,no;
        Seg(int a=0,int b=0,int c=0):l(a),r(b),no(c){}
        bool operator<(Seg x){
            return r<x.r;
        }
    }seg[M];
    int main(){
        int n;
        scanf("%d",&n);
        num_bit=n;
        for(int i=1;i<=n;++i)
            scanf("%d",&a[i]);
        int m;
        scanf("%d",&m);
        for(int i=1;i<=m;++i)
            scanf("%d%d",&seg[i].l,&seg[i].r),seg[i].no=i;
        sort(seg+1,seg+m+1);
        int p=0;
        for(int i=1;i<=m;++i){
            while(p<seg[i].r){
                ++p;
                update(p,1);
                if(last[a[p]])
                    update(last[a[p]],-1);
                last[a[p]]=p;
            }
            ans[seg[i].no]=query(seg[i].r)-query(seg[i].l-1);
        }
        for(int i=1;i<=m;++i)
            printf("%d
    ",ans[i]);
        return 0;
    }
    

    总结:

    树状数组基本到此为止,只要背模板直接套用即可。看起来并不需要精通原理。

    线段树

    最终还是用了wzk的风格,即每次只修改lazy,pushdown操作先修改当前节点,再将lazy下放到儿子。

    三道例题:

    1. P3372:区间加减+区间查询和
    #include<bits/stdc++.h>
    #define mid (l+r)/2
    #define LL long long
    using namespace std;
    const int M=1e5+20;
    LL v[4*M],a[M],lazy[4*M];
    inline void push_up(int id,int l,int r){
        v[id]=v[id*2]+lazy[id*2]*(mid-l+1)+v[id*2+1]+lazy[id*2+1]*(r-mid);
    }
    inline void push_down(int id,int l,int r){
        lazy[id*2]+=lazy[id],
        lazy[id*2+1]+=lazy[id],
        v[id]+=lazy[id]*(r-l+1),
        lazy[id]=0;
    }
    void build_tree(int id,int l,int r){
        if (l==r){
            v[id]=a[l];
            return;
        }
        build_tree(id*2,l,mid);
        build_tree(id*2+1,mid+1,r);
        push_up(id,l,r);
    }
    LL query(int id,int l,int r,int ql,int qr){
        if (ql<=l&r<=qr)
            return v[id]+lazy[id]*(r-l+1);
        push_down(id,l,r);
        LL ans=0;
        if (ql<=mid)
            ans+=query(id*2,l,mid,ql,qr);
        if (mid<qr)
            ans+=query(id*2+1,mid+1,r,ql,qr);
        return ans;
    }
    void operate(int id,int l,int r,int ql,int qr,LL x){
        if (ql<=l&&r<=qr){
            lazy[id]+=x;
            return;
        }
        push_down(id,l,r);
        if (ql<=mid)
            operate(id*2,l,mid,ql,qr,x);
        if (qr>mid)
            operate(id*2+1,mid+1,r,ql,qr,x);
        push_up(id,l,r);
    }
    int main(){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;++i)
            scanf("%lld",&a[i]);
        build_tree(1,1,n);
        for(int i=1;i<=m;++i){
            int op;
            scanf("%d",&op);
            if (op==1){
                LL x,y,k;
                scanf("%lld%lld%lld",&x,&y,&k);
                operate(1,1,n,x,y,k);
            }
            else{
                LL x,y;
                scanf("%lld%lld",&x,&y);
                printf("%lld
    ",query(1,1,n,x,y));
            }
        }
        return 0;
    }
    
    1. P3373:区间加减乘+区间查询和
    #include<bits/stdc++.h>
    #define mid (l+r)/2
    #define LL long long
    using namespace std;
    const int M=1e5+20;
    LL v[4*M],a[M],lazy1[4*M],lazy2[4*M],P;
    inline void push_up(int id,int l,int r){
        v[id]=(v[id*2]*lazy1[id*2]+lazy2[id*2]*(mid-l+1)+v[id*2+1]*lazy1[id*2+1]+lazy2[id*2+1]*(r-mid))%P;
    }
    inline void push_down(int id,int l,int r){
        v[id]=(v[id]*lazy1[id]+lazy2[id]*(r-l+1))%P;
        lazy1[id*2]=lazy1[id*2]*lazy1[id]%P;
        lazy1[id*2+1]=lazy1[id*2+1]*lazy1[id]%P;
        lazy2[id*2]=(lazy2[id*2]*lazy1[id]+lazy2[id])%P;
        lazy2[id*2+1]=(lazy2[id*2+1]*lazy1[id]+lazy2[id])%P;
        lazy1[id]=1,lazy2[id]=0;
    }
    void build_tree(int id,int l,int r){
        lazy1[id]=1,lazy2[id]=0;
        if (l==r){
            v[id]=a[l]%P;
            return;
        }
        build_tree(id*2,l,mid);
        build_tree(id*2+1,mid+1,r);
        push_up(id,l,r);
    }
    LL query(int id,int l,int r,int ql,int qr){
        if (ql<=l&r<=qr)
            return (v[id]*lazy1[id]+lazy2[id]*(r-l+1))%P;
        push_down(id,l,r);
        LL ans=0;
        if (ql<=mid)
            ans=(ans+query(id*2,l,mid,ql,qr))%P;
        if (mid<qr)
            ans=(ans+query(id*2+1,mid+1,r,ql,qr))%P;
        return ans;
    }
    void operate1(int id,int l,int r,int ql,int qr,LL x){
        if (ql<=l&&r<=qr){
            lazy1[id]=lazy1[id]*x%P;
            lazy2[id]=lazy2[id]*x%P;
            return;
        }
        push_down(id,l,r);
        if (ql<=mid)
            operate1(id*2,l,mid,ql,qr,x);
        if (qr>mid)
            operate1(id*2+1,mid+1,r,ql,qr,x);
        push_up(id,l,r);
    }
    void operate2(int id,int l,int r,int ql,int qr,LL x){
        if (ql<=l&&r<=qr){
            lazy2[id]=(lazy2[id]+x)%P;
            return;
        }
        push_down(id,l,r);
        if (ql<=mid)
            operate2(id*2,l,mid,ql,qr,x);
        if (qr>mid)
            operate2(id*2+1,mid+1,r,ql,qr,x);
        push_up(id,l,r);
    }
    int main(){
        int n,m;
        scanf("%d%d%d",&n,&m,&P);
        for(int i=1;i<=n;++i)
            scanf("%lld",&a[i]);
        build_tree(1,1,n);
        for(int i=1;i<=m;++i){
            int op;
            scanf("%d",&op);
            if (op==1){
                LL x,y,k;
                scanf("%lld%lld%lld",&x,&y,&k);
                operate1(1,1,n,x,y,k);
            }
            else if (op==2){
                LL x,y,k;
                scanf("%lld%lld%lld",&x,&y,&k);
                operate2(1,1,n,x,y,k);
            }
            else{
                LL x,y;
                scanf("%lld%lld",&x,&y);
                printf("%lld
    ",query(1,1,n,x,y));
            }
        }
        return 0;
    }
    
    1. P1198:单点修改+区间查询最大值
    //单点插入+区间最大值
    #include<bits/stdc++.h>
    #define mid (l+r)/2
    #define LL long long
    using namespace std;
    const int M=2e5+20;
    int v[4*M],D;
    char s[100];
    LL lastans,endpos;
    inline void push_up(int id,int l,int r){
        v[id]=max(v[id*2],v[id*2+1]);
    }
    int query(int id,int l,int r,int ql,int qr){
        if (ql<=l&&r<=qr)
            return v[id];
        int ans=-2e9-100;
        if (ql<=mid)
            ans=max(ans,query(id*2,l,mid,ql,qr));
        if (mid<qr)
            ans=max(ans,query(id*2+1,mid+1,r,ql,qr));
        return ans;
    }
    void operate(int id,int l,int r,int pos,int x){
        if (l==r){
            v[id]=x;
            return;
        }
        if (pos<=mid)
            operate(id*2,l,mid,pos,x);
        else
            operate(id*2+1,mid+1,r,pos,x);
        push_up(id,l,r);
    }
    int main(){
        LL ca;
        int n;
        scanf("%d%d",&n,&D);
        for(int i=1;i<=n;++i){
            scanf("%s",s);
            if (s[0]=='A'){
                scanf("%lld",&ca);
                operate(1,1,n,++endpos,(lastans+ca)%D);
            }
            else{
                LL ca;
                scanf("%lld",&ca);
                printf("%lld
    ",lastans=query(1,1,n,endpos-ca+1,endpos));
            }
        }
        return 0;
    }
    

    注意:如果不同操作是用不同字母来代替的话,一定要按照%s字符串读入,不然会死的很惨……

    总结:

    线段树的题目每天都要做,明天找一下区间加减或修改+区间查询最大值的题目,一定要好好熟练pushdown怎么写,记得上次就是这道题调不出来,很难顶。

    之后还有今天没写的题目,洛谷P2161。先按照自己的搜索写一下,之后再看看线段树染色的标解。

    单调队列

    今天学了学这两个单调数据结构,真是神仙……

    思路:用双端队列。队首元素可能需要维护,一般队首元素为答案。每次从队尾插入元素之前,先检验是否从头到尾是单调的,否则就从队尾弹出元素,直到单调时再插入元素。

    复杂度(O(n))

    能做的事情

    • (离线)求连续区间最值。
    • 可以配合二分查找答案。

    别的还在挖掘中……

    一道例题:

    1. POJ2823。给1e6个数和k,从左到右依次输出长度为k的区间内的最大值和最小值。

    若发现i<j&&a[i]>a[j],如5 3,那么从最小值意义上讲,5已经毫无用处了,因此插入3之前就直接扔掉5。对最大值同理。

    每次首先维护队首元素看是否在当前窗格内,否则弹出。之后从队尾插入元素时检验是否单调,到单调或空再插入。复杂度(O(n))

    注意:POJ上stl慢的一批,听说数组模拟队列很好用,回头学一下。

    //滑动窗口最大最小值
    #include<iostream>
    #include<cstdio>
    #include<utility>
    #include<queue>
    using namespace std;
    const int M=1e6+20;
    int a[M];
    #define pii pair<int,int>
    deque<pii> qmx,qmn;
    inline char nc() {
        static char buf[1000000], *p1 = buf, *p2 = buf;
        return p1 == p2 && (p2 = (p1 = buf) + fread (buf, 1, 1000000, stdin), p1 == p2) ? EOF : *p1++;
    }
    inline void read(int &sum) {
        char ch = nc();
        int tf = 0;
        sum = 0;
        while((ch < '0' || ch > '9') && (ch != '-')) ch = nc();
        tf = ((ch == '-') && (ch = nc()));
        while(ch >= '0' && ch <= '9') sum = sum * 10+ (ch - 48), ch = nc();
        (tf) && (sum =- sum);
    }
    int main(){
        int n,m;
        read(n),read(m);
        for(int i=1;i<=n;++i)
            read(a[i]);
        for(int i=1;i<=n;++i){
            if (!qmn.empty()&&qmn.front().second+m==i)
                qmn.pop_front();
            while(!qmn.empty()&&qmn.back().first>=a[i])
                qmn.pop_back();
            qmn.push_back(pii(a[i],i));
            if (i==m)
            	printf("%d",qmn.front().first);
            else if (i>m)
            	printf(" %d",qmn.front().first);
        }
        putchar('
    ');
        for(int i=1;i<=n;++i){
            if (!qmx.empty()&&qmx.front().second+m==i)
                qmx.pop_front();
            while(!qmx.empty()&&qmx.back().first<=a[i])
                qmx.pop_back();
            qmx.push_back(pii(a[i],i));
            if (i==m)
            	printf("%d",qmx.front().first);
            else if (i>m)
            	printf(" %d",qmx.front().first);
        }
        putchar('
    ');
        return 0;
    }
    
    

    单调栈

    原理和单调队列差不多,只不过这次是在栈上操作,而不是双端队列。每次弹入元素之前先检验是否单调,到单调或空再弹入。答案可能在弹出时进行计算。和单调队列一样,一般插入时还需要配上位置(编号)

    能做的事情

    • 左右第一个比某个位置的数大的数的位置,可以计算他们之间有几个数
    • 给定序列,寻找子序列,使得最小值*序列长度最大。
    • 给定序列,寻找子序列,使得最小值*序列元素和最大。

    一道例题:

    1. POJ3250。奶牛题。每头牛能看到右边严格矮于它的牛的头型,设能看到c[i]头牛,问c[i]和。

    单调栈,弹出的时候,被弹元素右边第一个比他大的数就是待插入元素,减去相应编号即可。同样也是离线处理。

    //右侧第一个比他小的值的位置
    #include<cstdio>
    #include<utility>
    #include<stack>
    using namespace std;
    const int M=1e5+20;
    int h[M],ans[M];
    #define pii pair<int,int>
    #define LL long long 
    LL res;
    void r_1max(int n){//需要ans,h数组
        stack<pii> st;
        for(int i=1;i<=n;++i){
            while(!st.empty()&&st.top().first<=h[i])
                ans[st.top().second]=i-st.top().second-1,st.pop();
            st.push(pii(h[i],i));
        }
        while(!st.empty())
            ans[st.top().second]=n-st.top().second,st.pop();
    }
    int main(){
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d",&h[i]);
        r_1max(n);
        for(int i=1;i<=n;++i)
            res+=ans[i];
        printf("%lld
    ",res);
        return 0;
    }
    
    

    总结:

    单调数据结构的用处在于,如果在维护东西的时候,有很多冗余元素,比如没用的5,那么就可以利用单调性,进行删除,方便得出答案。这部分的例题还有很多,明天把那几道经典题都做了。

    其他

    今天还做了一个优先队列的,POJ2431卡车加油题。这个直接想到了,就不写了。主要还是贪心。

    明日计划

    1. 线段树P2161,还有试炼场的另外一道题目
    2. 单调队列:HDU6319
    3. 单调栈:POJ2559,POJ3494,POJ2796,qz字符串CF1073G

    最近半个月拼了!

  • 相关阅读:
    win7 安装 redis +php扩展
    HTTP Status 500
    浅谈java8新特性之stream
    关于排序算法中的稳定与不稳定
    关于面试中的mysql试题1
    mysql关于case-when-end的使用
    eclipse项目启动报:Project facet Java version 1.8 is not supported
    mysql 5.7密码报错 ERROR 1820 (HY000): You must reset your password using ALTER USER statement before executing this statement.
    浅谈并行与并发
    浅谈Java垃圾回收(GC)
  • 原文地址:https://www.cnblogs.com/diorvh/p/11973994.html
Copyright © 2020-2023  润新知