zoukankan      html  css  js  c++  java
  • 换汤不换药——cdq分治

    cdq分治——它是一种通过计算前一半对后一半的影响的手段。

    直到今天才发现其实还是挺简单的,就是把一个区间分成两部分,然后看前半部分对后半部分的贡献,然后一路分治下去就好了。

    洛谷 P3810 【模板】三维偏序(陌上花开)

    三维偏序问题是二维偏序问题的升级版,二维偏序通常就是第一维排个序,第二维搞个树状数组等等来求出答案。

    但是三维呢?我们第一维依然是可以排序,现在只剩二维了,此时我们可以用树套树解决,动态问题的情况下这的确是种解法,但是在离线情况下,cdq分治有它的优势。

    我们把第二维通过cdq分治来降维,怎么做?

    我们把一个区间分成两部分,用双指针,第一个指针放在左区间,第二个指针放在右区间,不断判断左区间当前指针值是否对当前右区间指针值做出贡献,直到左区间的贡献结束。(类似归并排序求逆序对)

    而是否造成贡献,取决于它们的第三维坐标。贡献(第三维)就用树状数组来处理。

    #include <bits/stdc++.h>
    #define mp make_pair
    using namespace std;
    typedef long long ll;
    inline int read(){int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
    return s*w;}
    const int maxn = 200005;
    const int INF = 0x3f3f3f3f; 
    const int mod =998244353;
    struct node
    {
        int a,b,c,cnt,ans;
    }s[maxn],x[maxn];
    int n,m,k,top,ans[maxn],i;
    int c[maxn];
    bool cmp1(node x,node y)
    {
        if (x.a==y.a)
        {
            if(x.b==y.b)return x.c<y.c;
            else return x.b<y.b;
        }
        else return x.a<y.a;
    }
    bool cmp2(node x,node y)
    {
        if (x.b==y.b)
        return x.c<y.c;
        else return x.b<y.b;
    }
    int lowbit(int x)
    {
        return x & (-x);
    }
    void add(int x,int y)
    {
        while (x<=k)
        {
            c[x]+=y;
            x+=lowbit(x);
        }
    }
    int query(int x)
    {
        int sum=0;
        while(x)
        {
            sum+=c[x];
            x-=lowbit(x);
        }
        return sum;
    }
    void cdq(int l,int r)
    {
        if (l==r) return;
        int mid=(l+r)/2;
        cdq(l,mid);
        cdq(mid+1,r);
        sort(s+l,s+mid+1,cmp2);
        sort(s+mid+1,s+r+1,cmp2);
        int i,j=l;
        for (i=mid+1;i<=r;i++)
        {
            while(s[i].b>=s[j].b && j<=mid)
            {
                add(s[j].c,s[j].cnt);
                j++;
            }
            s[i].ans+=query(s[i].c);
        }
        for(i=l;i<j;++i)
            add(s[i].c,-s[i].cnt);
    }
    int main()
    {
        n=read();k=read();
        for (i=1;i<=n;i++) 
        {
            x[i].a=read();
            x[i].b=read();
            x[i].c=read();
        }
        sort(x+1,x+1+n,cmp1);
        for(int i=1;i<=n;++i)
        {
            top++;
            if(x[i].a!=x[i+1].a||x[i].b!=x[i+1].b||x[i].c!=x[i+1].c)
            {
                m++;
                s[m].a=x[i].a;
                s[m].b=x[i].b;
                s[m].c=x[i].c;
                s[m].cnt=top;
                top=0;
            }
        }
        cdq(1,m);
        for(int i=1;i<=m;++i)
            ans[s[i].ans+s[i].cnt-1]+=s[i].cnt;
        for(int i=0;i<n;++i) printf("%d
    ",ans[i]);
        return 0;    
    }
    View Code

    牛客https://ac.nowcoder.com/acm/contest/625/H

    求ans

     对于当前区间【l,r】而言,我们把他分治为【l,mid】、【mid+1、r】,于是我们根据cdq分治的原理前半部分对后半部分的贡献,我们算出左区间的一段gcd值,然后与右区间的每一段各自的gcd值,进行处理。这里我们用两个map来存取左右区间的gcd值,即可达到目的。这里要注意的是前半部分如何对后半部分产生贡献呢?我们可以看到左区间的gcd若要与右区间产生联系,那么肯定是从后往前的。比如,你不可能【l,l+1】这段区间的gcd值能与【mid+1,r】的gcd值关联,因为我们要保持连续性,所以只能【mid-x,mid】与【mid+1,r】联系起来,即要从后往前算gcd值。

    #include <bits/stdc++.h>
    #define mp make_pair
    using namespace std;
    typedef long long ll;
    inline int read(){int s=0,w=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
    return s*w;}
    const int maxn = 5e5+5;
    const int INF = 0x3f3f3f3f; 
    const int mod =1e9+7;
    map<int,int> G1,G2;
    int n,a[maxn];
    ll ans;
    ll gcd(ll a, ll b) { return b ? gcd(b,a%b) : a; }
    void solve(int l,int r)
    {
        if (l==r) 
        {
            ans=(ans+a[l])%mod;
            return;
        }
        int mid=(l+r)/2;
        solve(l,mid);
        solve(mid+1,r);
        int k=0;
        for (int i=mid;i>=l;i--)
        {
            k=gcd(k,a[i]);
            G1[k]++;
        }
        k=0;
        for (int i=mid+1;i<=r;i++)
        {
            k=gcd(k,a[i]);
            G2[k]++;
        }
        for (auto i:G1)
            for (auto j:G2)
            {
                ll num=gcd(i.first,j.first)*i.second%mod*j.second%mod;
                ans=(ans+num)%mod;
            }
        G1.clear();
        G2.clear();
    }
    int main()
    {
        n=read();
        for (int i=1;i<=n;i++) a[i]=read();
        solve(1,n);
        printf("%lld
    ",ans);
        return 0;
    }
    View Code

    说实话,在没有了解这个算法之前,我一直以为它十分的高深,因为前面有个cdq的名称。我以前的疑问:cdq是什么?人名……。普通分治上面还能怎么优化?其实只是在分治的前提下计算前半部分对后半部分的贡献……。以前看别人题解写“这题cdq分治水过啦”,???黑人问号,这算法听起来超级高深的亚子。

    总结:其实阻碍我们在acm道路上成长,不是这些算法有多么的高深,而是这些算法很多且杂,题目的种类也是繁多,萌新往往会被一个未成学过的算法给吓唬到,以至于解题无所适从。所以坚持就显得尤为重要,因为,你哪知道前方的风景会有多美。

  • 相关阅读:
    ffmpeg+EasyDSS流媒体服务器实现稳定的rtmp推流直播
    ffmpeg+EasyDSS流媒体服务器实现稳定的rtmp推流直播
    EasyPlayerPro安卓流媒体播放器实现Android H.265硬解码流程
    EasyPlayerPro安卓流媒体播放器实现Android H.265硬解码流程
    EasyPlayer RTSP安卓Android播放器架构简析
    EasyPlayer RTSP安卓Android播放器架构简析
    解决RTMP推送时间戳问题引起HLS切片不均匀导致手机浏览器播放卡顿的问题
    解决RTMP推送时间戳问题引起HLS切片不均匀导致手机浏览器播放卡顿的问题
    EasyPusher/EasyDarwin支持H.265 RTSP/RTP直播推流与分发播放
    EasyPusher/EasyDarwin支持H.265 RTSP/RTP直播推流与分发播放
  • 原文地址:https://www.cnblogs.com/Y-Knightqin/p/12577956.html
Copyright © 2011-2022 走看看