首页 > 其他分享 >CDQ分治

CDQ分治

时间:2023-12-17 14:44:23浏览次数:25  
标签:mathbf ll 分治 mid gx CDQ small now

CDQ分治

一般珂以替代一些较复杂的高级数据结构,能用来处理偏序问题、优化dp转移等。
大概思路就是分治处理点对的关系,分成三类点:\(\mathbf{\small{1}}\le i<j\le mid\)、\(\mathbf{\small{1}}\le i\le mid<j\le n\)、\(mid<i<j\le n\),然后用额外的 \(O(logn)\) 的时间去计算第二类点,一般是用树状数组或者线段树维护。

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

先按 \(a\) 为第一关键字排序,去重,然后分治。分治中我们要解决的是 \(b,c\) 的二维偏序,考虑使用树状数组求解,对两边序列分别按 \(b\) 为第一关键字排序,用双指针的维护方法不断在树状数组中统计答案,因为只会走一遍所以复杂度 \(O(nlogn)\)。最后统计答案的时候注意每个 \(i\) 实际答案是 \(ans_i+cnt_i-\mathbf{\small{1}}\)。

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=2*114514,M=1919810;
struct xx{
	ll a,b,c,cnt,ans;
	bool operator !=(const xx &lxl)const{
		return a!=lxl.a||b!=lxl.b||c!=lxl.c;
	}
}a[N];
ll n,m,s[N],res[N];
ll lowbit(ll x){return x&-x;}
void add(ll x,ll k){
	while(x<=m){
		s[x]+=k;
		x+=lowbit(x);
	}
}
ll query(ll x){
	ll ans=0;
	while(x){
		ans+=s[x];
		x-=lowbit(x);
	}
	return ans;
}
bool cmp1(xx x,xx y){
	return x.a==y.a?(x.b==y.b?x.c<y.c:x.b<y.b):x.a<y.a;
}
bool cmp2(xx x,xx y){
	return x.b==y.b?x.c<y.c:x.b<y.b;
}
void calc(ll l,ll r){
	if(l==r) return;
	ll mid=l+r>>1;
	calc(l,mid),calc(mid+1,r);
	sort(a+l,a+mid+1,cmp2),sort(a+mid+1,a+r+1,cmp2);
	ll j=l;
	for(int i=mid+1;i<=r;++i){
		while(a[i].b>=a[j].b&&j<=mid){
			add(a[j].c,a[j].cnt);
			++j;
		}
		a[i].ans+=query(a[i].c);
	}
	for(int i=l;i<j;++i) add(a[i].c,-a[i].cnt);
}
int main(){
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>a[i].a>>a[i].b>>a[i].c;
	sort(a+1,a+n+1,cmp1);
	ll tot=0,top=0;
	for(int i=1;i<=n;++i){
		++top;
		if(a[i]!=a[i+1]) a[++tot]=a[i],a[tot].cnt=top,top=0;
	}
	calc(1,tot);
	for(int i=1;i<=tot;++i) res[a[i].ans+a[i].cnt-1]+=a[i].cnt;
	for(int i=0;i<n;++i) cout<<res[i]<<'\n';
	return 0;
} 

P2487 [SDOI2011] 拦截导弹

先考虑朴素算法:第一问就直接 \(O(n^{\mathbf{\small{2}}})\) dp转移,第二问的话我们珂以多开几个数组:\(f\mathbf{\small{2}}_i\) 表示以 \(i\) 为开头的最长不上升子序列的长度,再开两个数组 \(g\mathbf{\small{1}},g\mathbf{\small{2}}\) 记录以 \(i\) 作为结尾和开头的最长不上升子序列的个数。这样子每个满足 \(f\mathbf{\small{1}}_i+f\mathbf{\small{2}}_i-\mathbf{\small{1}}=ans\) 的 \(i\) 的答案就是 \(\dfrac{g\mathbf{\small{1}}_i\times g\mathbf{\small{2}}_i}{sum}\),\(sum\) 为总方案数。求以 \(i\) 开头的答案珂以直接把序列倒过来权值赋为相反数然后正常dp。

考虑怎么优化,观察到这个最长不下降子序列就是个三维偏序问题,但是我们此时注意计算顺序是先计算左区间再算中间段再算右区间,否则dp的转移会有问题。这里我们分治时维护的要求变成了单点修改区间查询,但是我已经不会树状数组了,就用了线段树。还有这个题好像珂以直接 \(O(n)\) 清空整个线段树就免得还要另开数组记录?另外要开long double

code

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lb long double
#define ls now<<1
#define rs now<<1|1
const ll N=2*114514,M=1919810;
ll n,m,v[N];
struct xx{
	ll t;
	lb h,v;
}a[N],b[N],c[N];
struct gx{
	lb val,cnt;
}f1[N],f2[N];
bool cmp1(xx x,xx y){
	return x.t<y.t;
}
bool cmp2(xx x,xx y){
	return x.h==y.h?x.v>y.v:x.h>y.h;
}
gx add(gx x,gx y){
	return x.val==y.val?(gx){x.val,x.cnt+y.cnt}:(x.val>y.val?x:y);
}
struct tree{
	ll l,r;
	gx v;
}t[4*N];
void pushup(ll now){
	t[now].v=add(t[ls].v,t[rs].v);
}
void build(ll now,ll l,ll r){
	t[now].l=l,t[now].r=r;
	t[now].v=(gx){0,0};
	if(l==r) return;
	ll mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
}
void update(ll now,ll tar,gx k){
	if(t[now].l==t[now].r){
		t[now].v=add(t[now].v,k);
		return;
	}
	ll mid=t[now].l+t[now].r>>1;
	if(tar<=mid) update(ls,tar,k);
	else update(rs,tar,k);
	pushup(now);
}
gx query(ll now,ll x,ll y){
	if(t[now].l>=x&&t[now].r<=y) return t[now].v;
	ll mid=t[now].l+t[now].r>>1;
	gx ans=(gx){0,0};
	if(x<=mid) ans=add(ans,query(ls,x,y));
	if(y>mid) ans=add(ans,query(rs,x,y));
	return ans;
}
void clean(ll now,ll tar){
	t[now].v=(gx){0,0};
	if(t[now].l==t[now].r) return;
	ll mid=t[now].l+t[now].r>>1;
	if(tar<=mid) clean(ls,tar);
	else clean(rs,tar);
}
void calc(ll l,ll r,gx *dp){
	if(l==r){
		if(!dp[(ll)b[l].t].val)
			dp[(ll)b[l].t]=(gx){1,1};
		return;
	}
	ll mid=l+r>>1;
	calc(l,mid,dp);
	for(int i=mid+1;i<=r;++i) c[i]=b[i];
	sort(c+mid+1,c+r+1,cmp2);
	ll j=l;
	for(int i=mid+1;i<=r;++i){
		while(b[j].h>=c[i].h&&j<=mid){
			update(1,b[j].v,dp[(ll)b[j].t]);
			++j;
		}
		gx ans=query(1,c[i].v,m);
		if(ans.val){
			++ans.val;
			dp[(ll)c[i].t]=add(dp[(ll)c[i].t],ans);
		}
	}
	for(int i=l;i<=j;++i) clean(1,b[i].v);
	calc(mid+1,r,dp);
	sort(b+l,b+r+1,cmp2);
}
void solve(bool flag){
	for(int i=1;i<=n;++i) b[i]=a[i],v[i]=a[i].v;
	sort(b+1,b+n+1,cmp1);
	sort(v+1,v+n+1);
	m=unique(v+1,v+n+1)-v-1;
	for(int i=1;i<=n;++i) b[i].v=lower_bound(v+1,v+m+1,b[i].v)-v;
	build(1,1,m);
	if(!flag) calc(1,n,f1);
	else calc(1,n,f2);
}
lb ans,sum;
int main(){
	//ios::sync_with_stdio(0);
	//cin.tie(0); cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;++i) cin>>a[i].h>>a[i].v,a[i].t=i;
	solve(0);
	for(int i=1;i<=n;++i) a[i]=(xx){n-i+1,-a[i].h,-a[i].v};
	solve(1);
	for(int i=1;i<=n;++i) ans=max(ans,f1[i].val);
	for(int i=1;i<=n;++i) if(ans==f1[i].val) sum+=f1[i].cnt;
	printf("%Ld\n",(ll)ans);
	for(int i=1;i<=n;++i)
		if(f1[i].val+f2[n-i+1].val-1==ans) printf("%0.5Lf ",(f1[i].cnt*1.0*f2[n-i+1].cnt*1.0/sum));
		else printf("0.00000 ");
	return 0;
}

标签:mathbf,ll,分治,mid,gx,CDQ,small,now
From: https://www.cnblogs.com/heshuwan/p/17902876.html

相关文章

  • 归并分治
    归并排序是计算机之父"冯·诺依曼"发明的,但这其中隐藏了一种归并分治的思想。这种思想在一些题目中会帮助我们解决很多问题。原理对于一个大问题的答案,如果等于左边子问题的答案+右边子问题的答案+跨越左右问题的答案。在计算跨越左右问题的答案时,左右两边是有序的是否会......
  • 【合并排序链表】分治/优先队列
    合并两个排序链表模拟维护一个合并链表,每次添加两个排序链表中较小val的节点即可模拟代码publicListNodemergeTwo(ListNodea,ListNodeb){if(a==null)returnb;if(b==null)returna;ListNodeans=newListNode(0);Lis......
  • 241. 为运算表达式设计优先级(分治 +记忆化)
    Problem:241.为运算表达式设计优先级给你一个由数字和运算符组成的字符串expression,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以按任意顺序返回答案。生成的测试用例满足其对应输出值符合32位整数范围,不同结果的数量不超过示例1:输入:expression=......
  • [持续更新][数据结构][算法]涵盖线性表、栈、链表、队列、图、动态规划、分治递归、回
    备考考点整理内部排序表格树的主要考点二叉树的常考紧紧抓住\(n_0=n_2+1\)\(n=n_0+n_1+n_2...n_m\)\(n=n_1+2*n_2+3*n_3...m*n_m\)+1哈夫曼树没有度为1的结点,也就是\(n_1=0\)完全二叉树常考总结最大岛屿问题(dfs模板)#include<iostream>#include<algorith......
  • 树分治全家桶
    树分治全家桶树,(是种在路边的绿化植物)是图论当中的一种特殊图,(由于绿化环境作用非常优秀)由于特殊性质丰富,经常出现在我们身边。本文将主要讲述(如何植树)一种树上优美的暴力——树分治。树分治树分治可以将部分\(O(n^2)\)的暴力降至\(O(n\logn)\)级别,尤其适用于树上距离及其......
  • 【刷题记录】20231124 线段树分治
    做题记录:注意到每次相当于\(0\)后面加\(1\),\(1\)后面加\(0\),因此每次可以合并01和10然后将问题规模减半。黑白染色,白格子=lcm+1,黑格子=prime相乘。发现横着竖着有六个质数,斜着只用四个质数。调整一下顺序即可。状压DP。考虑S作为前缀max时S与U-S的排列方案数。S每......
  • 点分治
    点分治是一种在树上进行的分治,可以方便的求解树上路径等问题。例题:P3806【模板】点分治1给定一棵树,询问树上是否存在长度为k的路径。现在我们假设x为根节点,那么一条路径长度为k有两种情况,一种是经过x,一种不经过x,第一种的两个端点在两个不同子树中,第二种的两个端点在同一......
  • 分治与归并
    归并算法:递归+合并,在递归的途中进行分治。递归会把区间越分越小,此时就可以进行分治操作。可以使用全局变量进行分治操作。可以在函数中进行分治操作,在递归树中实现pushup和pushdown,记性父节点与子节点的关系计算。  classSolution{public:structNode{......
  • 点分治学习笔记(未完成)
    前言点分治不应该算数据结构,它的本质是分治的思想。问题引入对于一个序列\(a\),求是否存在\((l,r)\)使得\(\sum\limits_{i=l}^{r}a_i=k\)。\(n\le10^6,|a_i|\le10^9\)。本题显然是有其它的做法的,由于学的是点分治,所以考虑分治做法。首先对\(a\)求前缀和,记这个数组为......
  • 算法学习笔记(1):CDQ分治
    CDQ分治对比普通分治把一种问题划分成不同子问题,递归处理子问题内部的答案,再考虑合并子问题的答案。再看CDQ分治有广泛的应用,但我不会。但在接下来的题目体会到大概:将可能产生的对于答案的贡献分为两类:\(f(l,mid)\)与\(f(mid+1,r)\)内部产生的贡献,其贡献已在递......