换汤不换药——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\n",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\n",ans);
    return 0;
}
View Code

 

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

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

posted @ 2020-03-26 22:27  Y-KnightQin  阅读(200)  评论(0编辑  收藏  举报