• 纪念神九发射作业(1)


    今天rabbithu和陈独秀……给我们留了八道提高+~NOI难度的题目……蒟蒻今天只做出来三道……还是别人给讲的……

    不管怎么说,写题解记录下来……不然以后忘了这种题怎么做咋整……

    C题:CQOI2018 异或序列 

    这道题可以看出来是莫队的板子题,不过一开始还是看得我一脸懵逼……

    由于异或的逆运算是其本身,那么我们就可以想办法解决这道题中最关键的一点——如何在分块之后对于每个访问的区间都能O(1)的求出其内部异或和等于k的子区间有多少个。

    基础的莫队是用桶来记录一个数出现了多少次,而且他是每次移动一个点,单点修改的,既然如此,那我们也可以仿照这种思路来写。

    首先是如何处理一些不与访问区间端点相连的子区间的问题,这个仔细想想发现很容易,因为我们是一个一个点修改的,这样我们会记录下来所有的其异或前缀和^k符合条件的点。

    再者,对于序列内任意一个数a[l],a[l] = sum[l-i]^sum[l],其中sum记录异或前缀和。再根据异或的逆运算是其本身的性质,我们直接在每次更新莫队的时候,把当前这个点的异或前缀和压到桶中,并且在总和上记录该异或前缀和再异或k的值在当前的莫队中有多少个即可。这样每次增加一个点,我们就相当于增加了一段异或区间和等于k的区间。删除与之道理相同。

    最后还有一点,一开始的桶要初始化,把cnt[0]设为1,因为你在计算右端点为l的访问区间的时候,必须要用到l-1,所以我们先手压入一个0以正常工作。

    上代码啦。

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm> 
    #include<cmath>
    #include<queue>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    const int M = 100005;
    const int B = ceil(sqrt(M));
    #define bel(x) ((x - 1) / B + 1)//分块
    typedef long long ll;
    struct node
    {
        int l,r,id;
        bool operator  < (const node &b) const//重载运算符进行排序
        {
            return bel(l) == bel(b.l) ? r < b.r : l < b.l;
        }
    }q[M];
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            ans *= 10;
            ans += ch - '0';
            ch = getchar();
        }
        return ans * op;
    }
    int n,m,k,a[M],cnt[M],sum,ans[M];
    void insert(int x)//插入和删除
    {
        sum += cnt[x^k];
        cnt[x]++;
    }
    void del(int x)
    {
        cnt[x]--;
        sum -= cnt[x^k];
    }
    int main()
    {
        n = read(),m = read(),k = read();
        rep(i,1,n) a[i] = read(),a[i] ^= a[i-1];
        rep(i,1,m) q[i].l = read(),q[i].r = read(),q[i].id = i;
        sort(q+1,q+1+m);
        int kl = 1,kr = 0;cnt[0] = 1;//这里是为了计算以1为开头的区间的前缀和 
        rep(i,1,m)//正常莫队操作,注意顺序
        {
            while(kr < q[i].r) insert(a[++kr]);
            while(kr > q[i].r) del(a[kr--]);
            while(kl < q[i].l) del(a[kl-1]),kl++;
            while(kl > q[i].l) kl--,insert(a[kl-1]);
            ans[q[i].id] = sum;
        }
        rep(i,1,m) printf("%d
    ",ans[i]);
        return 0;
    }

    F题:luogu3801 红色的幻想乡

    rabbithu说D题最简单……然而分明是F题最简单……

    来了个贼强的无限放雾的dalao。这题一开始想到二维线段树(然而根本就不会),不过后来发现并不需要。因为这姐们每次从一个点放雾是向横,纵向无限长放雾,那么我们在维护某一向的值得时候,就不用维护这一向的每一点在另一方向上的长度了。

    而且,她每次放雾是自己所在的位置没雾,而且两股雾在一起会抵消哦,也就是相当于她自己先在纵向上放一列,又在横向上放一行,结果自己内个位置因为放过两次抵消了,这样就可以用异或操作修改了。

    到这里思路已经很清晰了,建两棵线段树,一棵维护纵向,一颗维护横向,那么怎么计算呢?

    对于其给定的每一个矩形区域,是可以保证区域中只要有红雾的行、列一定充满了红雾,我们只要先query一遍这个矩形长方向上的红雾和,在query一遍纵向的,之后把横向上的乘以矩形纵向长,纵向乘以矩形横向长,最后再用容斥原理,减去二倍的交点数即可。(注意这里一定不要乘反!我就是乘反才爆零)

    本题数据到10^5,别以为其平方小于2147483647就无忧了,你在相加的过程中是可能溢出的,要开longlong。之后就轻松A了。

    上代码。

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm> 
    #include<cmath>
    #include<queue>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    const int M = 100005;
    typedef long long ll;
    struct seg
    {
        ll v;
    }t1[M<<2],t2[M<<2];
    ll read()
    {
        ll ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            ans *= 10;
            ans += ch - '0';
            ch = getchar();
        }
        return ans * op;
    }
    ll n,m,q,op,x,y,kx,ky,sx,sy,l,w,k1,k2,d;
    void modify1(ll p,ll l,ll r,ll x)//这题真的只需要单点修改,modify2照抄
    {
        if(l == r)
        {
            t1[p].v ^= 1;
            return;
        }
        ll mid = (l+r) >> 1;
        if(x <= mid) modify1(p<<1,l,mid,x);
        else modify1(p<<1|1,mid+1,r,x);
        t1[p].v = t1[p<<1].v + t1[p<<1|1].v;
    }
    void modify2(ll p,ll l,ll r,ll x)
    {
        if(l == r)
        {
            t2[p].v ^= 1;
            return;
        }
        ll mid = (l+r) >> 1;
        if(x <= mid) modify2(p<<1,l,mid,x);
        else modify2(p<<1|1,mid+1,r,x);
        t2[p].v = t2[p<<1].v + t2[p<<1|1].v;
    }
    ll query1(ll p,ll l,ll r,ll kl,ll kr)//query与普通线段树相同,query2照抄
    {
        if(l == kl && r == kr) return t1[p].v;
        ll mid = (l+r) >> 1;
        if(kr <= mid) return query1(p<<1,l,mid,kl,kr);
        else if(kl > mid) return query1(p<<1|1,mid+1,r,kl,kr);
        else return query1(p<<1,l,mid,kl,mid) + query1(p<<1|1,mid+1,r,mid+1,kr);
    }
    ll query2(ll p,ll l,ll r,ll kl,ll kr)
    {
        if(l == kl && r == kr) return t2[p].v;
        ll mid = (l+r) >> 1;
        if(kr <= mid) return query2(p<<1,l,mid,kl,kr);
        else if(kl > mid) return query2(p<<1|1,mid+1,r,kl,kr);
        else return query2(p<<1,l,mid,kl,mid) + query2(p<<1|1,mid+1,r,mid+1,kr);
    }
    int main()
    {
    //    freopen("a.in","r",stdin);
        n = read(),m = read(),q = read();
        rep(i,1,q)
        {
            op = read();
            if(op == 1) 
            {
                x = read(),y = read();
                modify1(1,1,n,x);
                modify2(1,1,m,y);
            }
            if(op == 2)
            {
                kx = read(),ky = read(),sx = read(),sy = read();
                l = sx-kx+1,w = sy-ky+1;//计算矩形长宽
                k1 = query1(1,1,n,kx,sx);
                k2 = query2(1,1,m,ky,sy);
    //            printf("%d %d %d %d
    ",l,w,k1,k2);
                d = k1 * w + k2 * l - 2 * k1 * k2;//用容斥原理计算被红雾覆盖块数
                printf("%lld
    ",d);
            }
        }
        return 0;
    }

    H题:HEOI/TJOI2016 排序

    这道题真的对我来说很有难度……如果不是听人讲的话真的做不出来……算法思路很好理解……不过有许多坑要注意。

    听说这是一道套路题emm?

    不管啦,直接开讲。这道题如果直接暴力模拟sort的话,是可以拿到30,那我们再想想,sort之所以不行的原因,是因为其每次操作nlogn,再乘以sort的次数必然不行,那么有什么方法能更快地排序呢……

    显然普通方法是不可能的,那我们就有别的方法了……这是一个极好的套路,也是一个极为值得学习的方法——把所有数字转化为01串!

    这样排序的问题就迎刃而解了。我们只需要先手统计出你要sort的区间有多少个1(k个),之后直接对起始~末尾-k和末尾-k+1~末尾做区间修改即可,全改成0或者1,贼舒服,升降序全搞定。每次sort只需要2logn的时间。

    那么,我们接下来该干嘛呢……我们如何能通过01串来确定你要访问的位置上的数是多少,这是个关键。我们想,不过怎么排序,因为题目给定的排序只有一种顺序,照着他所说的排完你要的位置自然是答案,所以无论你是转化为01串还是什么其他的串,那个点上应该有的数是不会变的!那我们怎么用01串确定呢?

    二分答案!每次把大于等于当前二分值得数设为1,小于的为0,这样的话sort结束之后如果要访问的位置如果是0的话那就说明你的数取大了(因为0映射一个小于当前二分值的数,而实际上那一位应该是1),否则就是取小或者正好等于,那么直接这么二分下去就可以了。算法思路十分清晰,二分答案之后每次新建一棵树,之后对其按照要求进行多次区间修改,最后返回要访问的那一位的值即可。二分logn,每次建树+区间修改mlogn,在n,m <= 10^5的情况下可以过。

    但是这个题坑贼多。

    1.你打lazy标记的时候,初始值要设为-1,因为你是直接赋值修改,而不是加,所以不想在加法操作中lazy'为0就可以随便操作,这里是不行的!

    2.你modify的时候,由于query出来的1 的个数可能很多,有可能导致操作越界。

    这时候你想,啊,那我直接去区间端点和要修改的端点的极值不就行了吗。不!你这样确实是不会RE,但是再想,比如你长度为4 的区间有四个1,你如果取端点,相当于你还是把一个端点值改为0,但你实际上是不应该改的!

    所以我们的方法是在函数里加特判,其他啥也不要动!

    3.线段树别写跪了……

    就这些……然后就A了人生中第3道紫题。而且这个题的思路是十分值得学习的,这次理解了,也不知道以后能不能用出来……

    上代码。

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm> 
    #include<cmath>
    #include<queue>
    #define rep(i,a,n) for(int i = a;i <= n;i++)
    #define per(i,n,a) for(int i = n;i >= a;i--)
    #define enter putchar('
    ')
    
    using namespace std;
    const int M = 100005;
    typedef long long ll;
    struct seg
    {
        int v;
    }t[M<<2];
    struct opera
    {
        int l,r,op;
    }o[M]; 
    int read()
    {
        int ans = 0,op = 1;
        char ch = getchar();
        while(ch < '0' || ch > '9')
        {
            if(ch == '-') op = -1;
            ch = getchar();
        }
        while(ch >= '0' && ch <= '9')
        {
            ans *= 10;
            ans += ch - '0';
            ch = getchar();
        }
        return ans * op;
    }
    int n,m,a[M],op,dl,dr,q,lazy[M<<2],mi,ans;
    void build(int p,int l,int r,int x)//建树
    {
        if(l == r) 
        {
            t[p].v = (a[l] >= x)? 1:0;//按大小来确定01
            return;
        }
        int mid = (l+r) >> 1;
        build(p<<1,l,mid,x);
        build(p<<1|1,mid+1,r,x);
        t[p].v = t[p<<1].v + t[p<<1|1].v;
    }
    
    void down(int p,int l,int r)//注意lazy标记下放过程!
    {
        int mid = (l+r) >> 1;
        if(lazy[p] == -1) return;
        lazy[p<<1] = lazy[p];
        lazy[p<<1|1] = lazy[p];
        t[p<<1].v = lazy[p] * (mid-l+1);
        t[p<<1|1].v = lazy[p] * (r - mid);
        lazy[p] = -1;
    }
    
    int query(int p,int l,int r,int kl,int kr)//正常查询
    {
        if(l == kl && r == kr) return t[p].v;
        down(p,l,r);
        int mid = (l+r) >> 1;
        if(kr <= mid) return query(p<<1,l,mid,kl,kr);
        else if(kl > mid) return query(p<<1|1,mid+1,r,kl,kr);
        else return query(p<<1,l,mid,kl,mid) + query(p<<1|1,mid+1,r,mid+1,kr);
    }
    
    void modify(int p,int l,int r,int kl,int kr,int val)//正常修改
    {
        if(kl > kr || kl < l || kr > r) return;//这句话贼重要!少了就RE!
        if(l == kl && r == kr)
        {
            t[p].v = val * (r-l+1);
            lazy[p] = val;
            return;
        }
        down(p,l,r);
        int mid = (l+r) >> 1;
        if(kr <= mid) modify(p<<1,l,mid,kl,kr,val);
        else if(kl > mid) modify(p<<1|1,mid+1,r,kl,kr,val);
        else modify(p<<1,l,mid,kl,mid,val),modify(p<<1|1,mid+1,r,mid+1,kr,val);
        t[p].v = t[p<<1].v + t[p<<1|1].v;
    }
    bool check(int x)
    {
        memset(t,0,sizeof(t));
        memset(lazy,-1,sizeof(lazy));
        build(1,1,n,x);//先手建树
        rep(i,1,m)
        {
            int k = query(1,1,n,o[i].l,o[i].r);//查询区间中1的个数
            if(!o[i].op)//升序排列
            {
                modify(1,1,n,o[i].l,o[i].r-k,0);
                modify(1,1,n,o[i].r-k+1,o[i].r,1);//这里注意不要乱改端点值,加特判即可
            }
            else//降序
            {
                modify(1,1,n,o[i].l,o[i].l+k-1,1);
                modify(1,1,n,o[i].l+k,o[i].r,0);
            }
        }
        return query(1,1,n,q,q);//返回单点查询值
    }
    int main()
    {
        n = read(),m = read();
        rep(i,1,n) a[i] = read();
        rep(i,1,m) o[i].op = read(),o[i].l = read(),o[i].r = read();//离线操作
        q = read();
        dl = 1,dr = n;
        while(dl <= dr)//二分解决
        {
            mi = (dl + dr) >> 1;
            if(check(mi)) dl = mi+1,ans = mi;
            else dr = mi-1;
        }
        printf("%d
    ",ans);
        return 0;
    }
  • 相关阅读:
    [HAOI2016]食物链
    [TJOI2011]序列
    P2548 [AHOI2004]智能探险车
    [JSOI2008]最大数
    模板之dinic
    Excim的NOIP2017游记
    数列排序
    Car的旅行路线
    [HAOI2006]均分数据
    [luogu2210] Haywire
  • 原文地址:https://www.cnblogs.com/captain1/p/9191878.html
Copyright © 2020-2023  润新知