CDQ分治

CDQ分治,基于分治。基本模型就是归并排序,一般的分治两个子问题之间是没有影响的。而CDQ中却是相互影响的。
更理论的分析
更理论的分析
首先还是最基本的逆序对。

#include<bits/stdc++.h>
#define MAXN 100000
 using namespace std;
int n;
unsigned long long ans = 0;
int A[MAXN],B[MAXN];

void CDQ(int l,int r)
{
  if(l == r) return;
  int mid = (l+r) >> 1;
  CDQ(l,mid); CDQ(mid+1,r);
  int t1 = l , t2 = mid+1; 
  for(int i = l ; i <= r; i ++ )
  {
    if((t1 <= mid && A[t1] <= A[t2]) || t2 > r)
	 B[i] = A[t1++];
	else 
	{
	 B[i] = A[t2++];
	 ans += mid -(t1-1);
	} 		      
  }
  for(int i = l ; i <= r; i ++) A[i] = B[i];
}
int main()
{		
 cin>>n;
 for(int i = 1 ; i <= n ;i ++) scanf("%d",&A[i]);
 CDQ(1,n);
 cout<<ans;
 return 0;	
} 

这里中在分治过程中每当发现前一个数有大于后一个数,那么逆序对个数就是前一个数加上前一段中比前一个数大的数的个数。因为是排序过程,是有序的。
CDQ 的大致形状就是基于这里的,有排序过程。

下面介绍二维偏序问题。
hluoj 556 炉石传说
题目大意就是 给定 n 个两元组 a b ,某一个两元组 X 完胜另一个两元组 Y ,应该满足X.a > Y.a && X.b > Y.b 求的是每一个两元组能完胜的其他两元组的个数。
题解: 典型的二维偏序,将一维先排序,对于另一维来说,也就是求一个顺序对,将上面的代码中的ans += mid -(t1-1) 改为 ans += t1-L 注意这里的ans 对应一个两元组。
code:

#include<bits/stdc++.h>
#define MAXN  2000000
 using namespace std;
struct node{int a,b,id;};
node A[MAXN],B[MAXN];
int ans[MAXN],n;
bool myc(node x,node y){ if(x.a == y.a) return x.b < y.b; else return x.a < y.a; }
void init()
{
  cin>>n;
  for(int i = 1 ;i <= n;i++)
   scanf("%d%d",&A[i].a,&A[i].b),A[i].id = i;
  sort(A+1,A+1+n,myc);
}
void CDQ(int l,int r)
{
   if(l==r) return;
   int mid = l+r >> 1 ;
   CDQ(l,mid); CDQ(mid+1,r);
   int len1 = l ,len2 = mid+1;
   for(int i = l ;i <= r; i ++)
   {
   	if(len1 <= mid && A[len1].b <= A[len2].b || len2 > r)
      B[i] = A[len1++];
    else ans[A[len2].id] += len1-l , B[i] = A[len2++];
   }
   for(int i = l ;i <= r; i++) A[i] = B[i];
}
int main()
{
  freopen("2026.in","r",stdin);
  freopen("2026.out","w",stdout);
  init();
  CDQ(1,n);
  for(int i = 1 ;i <= n ; i ++) printf("%d\n",ans[i]);
  return 0;
} 

二维偏序的拓展题:
hluoj 557 数列求和
题目大意是给定一个长度为 n 的序列,有两种操作, 1 是单点修改。2 是区间求和
题解:当然可以用很多方法做,但这里讨论CDQ分治。
第一维是时间,第二维是操作类型和操作方法(包括对于哪个位置的修改)。还有的处理就是,把初值看做是一次单点修改,区间求和看做是对1 到 l 求和,1 到 r 求和。若为 l 贡献是负值,为 r 则是正值 。所以这样下,时间是有序的,前一个时间点的操作就有可能对后一个时间点的操作产生影响,但真正要产生影响的是?单点修改的位置在区间内,单点修改是 操作 1 ,左端点是 操作 2,右端点是操作 3。操作也要 前一个小于后一个 才对后一个有影响。
code:

#include<bits/stdc++.h>
#define ll long long 
#define MAXN  1000000
 using namespace std;
struct node{
  int id,type;
  ll val;
  bool operator < (const node &a) const
  {
    if(id != a.id) return id < a.id;
     else return type < a.type;
  }
};
node A[MAXN],B[MAXN];
ll ans[MAXN];
int tot = 0,totx = 0; // tot 表示总操作次数,totx表示查询次数 
int n,m;
void CDQ(int l,int r)
{
  if(l == r) return;
  int mid = l+r >> 1;
  CDQ(l,mid); CDQ(mid+1,r);
  int len1 = l ,len2 =mid+1;
  ll sum = 0; 
  for(int i = l; i <= r; i++)
  {
    if( (len1 <= mid && A[len1] < A[len2]) || len2 > r)
    {
      if(A[len1].type == 1 ) sum += A[len1].val;
      B[i] = A[len1++];
	}
    else 
  	{
      if(A[len2].type == 3 ) ans[A[len2].val] += sum;
	   else if(A[len2].type == 2) ans[A[len2].val] -= sum;		
  	  B[i] = A[len2++];                                                 
    }
  }
  for(int i = l; i <= r ;i++) A[i] = B[i];	
}
int main()
{	
  freopen("shulie.in","r",stdin);
  freopen("shulie.out","w",stdout);
  scanf("%d",&n);   
  for(int i = 1 ; i <= n ;i++)
  {
   tot++;	
   A[tot].id = i; A[tot].type = 1;  
   scanf("%lld",&A[tot].val);	
  }
  scanf("%d",&m);
  for(int i = 1 ;i <= m ; i ++)
  {
   int type1;
   tot++;
   string st;
   cin>>st;
   if(st[0]=='S') type1 = 2 ;
    else type1 = 1;
   A[tot].type = type1;
   if(type1 == 1 )
   {
    scanf("%d%lld",&A[tot].id,&A[tot].val);
   }
   else 
   {
   	int l,r;
   	totx++;
    scanf("%d%d",&l,&r);
    A[tot].id = l-1; A[tot].val = totx; tot++;
    A[tot].id = r;  A[tot].val = totx; A[tot].type = 3;
   }
  }
  CDQ(1,tot);
  for(int i = 1 ;i <= totx; i ++) printf("%lld\n",ans[i]);
  return 0;	
}

那么添加一维呢?
hluoj 陌上花开
题目大意和炉石传说差不多,添加一维c。
题解:同样是上一题的思路,先将第一维排序,第二维进行CDQ分治。只是当找到一个满足条件的 X.b > Y.b 还要考虑此时比 X 的 c 小的有几个加入答案。用BIT维护,这里我们每一层先进行排序,便于统计c,即每一次找的 b 满足 在树状数组中查一下有多少c 满足 加入答案。另外此题还有细节是可能存在相同的三元组,先要进行合并。输出的不在是每个三元组的答案,而是满足某个答案的三元组个数。最后用答案去更新即可。

#include<bits/stdc++.h>
#define MAXN  1001000
 using namespace std;
struct flo{
  int a,b,c,cn,ans;
}A[MAXN];
bool operator==(flo x,flo y)
{
  return x.a==y.a && x.b==y.b && x.c==y.c;
}
bool operator<(flo x,flo y)
{
  if(x.a != y.a) return x.a<y.a;
  if(x.b != y.b) return x.b<y.b;
   return x.c<y.c;
}
bool operator>(flo x,flo y)
{
  if(x.b != y.b) return x.b < y.b;
  return x.c < y.c; 
} 
inline bool myc(flo x,flo y){ return x>y; }
int n,k;
// BIT
int BIT[MAXN],ans[MAXN];
inline void ADD(int x,int v) {for( ; x <= k ; x+= x & -x ) BIT[x]+=v;}
inline int GET(int x) { int sum = 0; for( ; x; x-= x & -x ) sum+=BIT[x]; return sum; }

void CDQ(int l,int r)
{
  if(l == r) return ;
  int mid = (l+r) >>1;
  CDQ(l,mid); CDQ(mid+1,r);
  sort(&A[l],&A[mid+1],myc);
  sort(&A[mid+1],&A[r+1],myc);
  int t1 = l;
  for(int t2 = mid+1 ;t2 <= r ; t2++ )
  {
   while(t1 <= mid && A[t1].b <= A[t2].b) ADD(A[t1].c,A[t1].cn),t1++;
   A[t2].ans += GET(A[t2].c);	
  }
  for(int i = l ; i < t1 ;i ++) ADD(A[i].c,-A[i].cn);
}
void init()
{
  cin>>n>>k;
  for(int i = 1;i <= n ;i ++)
   scanf("%d %d %d",&A[i].a,&A[i].b,&A[i].c);
  sort(A+1,A+n+1);
  int len = 0;
  for(int i = 1 ; i <= n ;i ++)
  {
   if(A[i] == A[i-1]) A[len].cn++;
  	else A[++len] = A[i], A[len].cn = 1 ;
  }
  
  CDQ(1,len);
  for(int i = 1 ; i <= len ;i ++)  ans[A[i].ans+A[i].cn-1] += A[i].cn; 
  for(int i = 0 ; i < n ;i ++) printf("%d\n",ans[i]);  
}
int main()
{
  freopen("flower.in","r",stdin);
  freopen("flower.out","w",stdout);
  init();
  return 0;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值