Loading

学习笔记(1)cdq分治与传统二分

· 传统分治

主要思想是把大规模的问题化小规模后解决,后合并两区间统计答案(一般通过二分的思想取 \(mid\) 实现)

· 例题

\(P1257\) 平面上的最接近点对

\(P1429\) 平面最近点对(加强版)

\(P7883\) 平面最近点对(加强加强版)

分析:(不想写)

关于 \(O(n^2)\) 暴力操过加强版数据这回事:

#include <bits/stdc++.h>
#define N 200005
#define INF 0x7f7f7f7f7f7f7f7f 
#define ll long long 
using namespace std;
int n;
ll ans=INF,now,len;
struct node{
	ll x,y;
}a[N];
bool cmp(node at,node bt){
	if(at.x==bt.x) return at.y<bt.y;
	return at.x<bt.x;
}
ll dis(ll int x1,ll int y1,ll int x2,ll int y2){
	return ((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++){
		ll now=INF;
		for(int j=i+1;j<=n;j++){
			ll res=dis(a[i].x,a[i].y,a[j].x,a[j].y);
			ans=min(ans,res);
			now=min(now,res);
			if(res>now){
				if(j==n) break;
				res=dis(a[i].x,a[i].y,a[j+1].x,a[j+1].y);
				ans=min(ans,res);
				break;
			}
		}
	}
	printf("%0.4lf",sqrt(ans));
	return 0;
}

正解:

#include <bits/stdc++.h>
#define N 400005
#define ll long long
#define INF 0x7f7f7f7f7f7f7f7f
using namespace std;
int n,temp[N];
ll int ans;
struct node{
	ll int x,y;
}a[N];
bool cmp(node at,node bt){
	if(at.x==bt.x) return at.y<bt.y;
	return at.x<bt.x;
}
bool cmp2(int at,int bt){
	return a[at].y<a[bt].y;
}
ll dis(int lt,int rt){
	return (a[lt].x-a[rt].x)*(a[lt].x-a[rt].x)+(a[lt].y-a[rt].y)*(a[lt].y-a[rt].y);
}
ll solve(int l,int r){
	ll res=INF;
	if(l==r) return res;
	if(l+1==r) return dis(l,r);
	int mid=(l+r)>>1, now=0;
	res=min(solve(l,mid),solve(mid+1,r));
	for(int i=l;i<=r;i++){
		if((a[i].x-a[mid].x)*(a[i].x-a[mid].x)<res) temp[++now]=i; 
	}
	sort(temp+1,temp+now+1,cmp2);
	for(int i=1;i<=now;i++){
		for(int j=i+1;j<=now && (a[temp[j]].y-a[temp[i]].y)*(a[temp[j]].y-a[temp[i]].y)<res;j++){
			res=min(res,dis(temp[i],temp[j]));
		}
	}
	return res;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,cmp);
	ans=solve(1,n);
	printf("%lld",ans); //加强加强版
	return 0;
}

· \(cdq\) 分治

通过划分区间减小规模解决问题的思想一样,着重于统计前一个区间对后一个区间的答案的影响(或者后一个区间对前一个区间的影响)所贡献的答案,而非简单合并,该思想由陈丹琦引入

由于能够多牺牲 \(O(logn)\) 的时间降低问题维度(仅一维,可多重嵌套),常用于解决偏序问题(其常数及空间需求均小于高级数据结构写法,然而一个巨大的缺点是只能离线,会被卡在线、)解决多维偏序问题中的其中一维限制。二维偏序问题的形式即递归排序求解逆序对的过程,三维偏序问题可选择在一维有序/排序后的基础上两重 \(cdq\) 分治/树套树/ \(cdq\) 分治+树状数组解决;

· 例题

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

分析:对第一维 \((a)\) 排序,第二维 \((b)\) 进行 \(cdq\) 分治,分治过程中对前后两段区间分别按第二关键字 \((b)\) 排序,将第三维 \((c)\) 加入树状数组求比第 \(i\) 个元素小的元素个数,最后用 \(f\) 数组统计答案即可。

代码:

#include <bits/stdc++.h>
#define N 250005
using namespace std;
int n,kk,cnt,len;
int f[N],t[N];
int lowbit(int x){return x&(-x);}
void modify(int x,int k){
	while(x<=kk){
		t[x]+=k;
		x+=lowbit(x);
	}
}
int query(int x){
	int res=0;
	while(x){
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}
struct node{
	int a,b,c,tot,ans;
}l[N],num[N];
bool cmp1(node x,node y){
	if(x.a==y.a){
		if(x.b==y.b) return x.c<y.c;
		return x.b<y.b;
	}
	return x.a<y.a;
}
bool cmp2(node x,node y){
	if(x.b==y.b) return x.c<y.c;
	return x.b<y.b;
}
void cdq(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid);
	cdq(mid+1,r);
	sort(num+l,num+mid+1,cmp2);
	sort(num+mid+1,num+r+1,cmp2);
	int j=l;
	for(int i=mid+1;i<=r;i++){
		while(j<=mid && num[j].b<=num[i].b){
			modify(num[j].c,num[j].tot); ++j;
		}
		num[i].ans+=query(num[i].c);
	}
	for(int i=l;i<j;i++) modify(num[i].c,-num[i].tot);
}
int main(){
	scanf("%d%d",&n,&kk);
	for(int i=1;i<=n;i++) scanf("%d%d%d",&l[i].a,&l[i].b,&l[i].c);
	sort(l+1,l+n+1,cmp1);
	for(int i=1;i<=n;i++){
		++cnt;
		if(l[i].a!=l[i+1].a || l[i].b!=l[i+1].b || l[i].c!=l[i+1].c){
			num[++len]=l[i];
			num[len].tot=cnt;
			cnt=0;
		}
	}
	cdq(1,len);
	for(int i=1;i<=len;i++) f[num[i].ans+num[i].tot-1]+=num[i].tot;
	for(int i=0;i<n;i++) printf("%d\n",f[i]);
	return 0;
}

\(P3157\) 动态逆序对

分析:将删除顺序作为第三维限制,即可将原问题转换为三维偏序问题。对于每一个数删除之前的逆序对数,统计每一个数的贡献(由于对 \(cdq\) 分治时对前后都会有贡献,所以统计对前后两段分别求逆序对数),用总逆序对数 \(sum\) 依次减去每个数的贡献即可得到答案(懒得挨个减可以写个前缀和)

(冗杂的)代码:

#include <bits/stdc++.h>
#define N 100005
#define INF 0x7fffffff
#define ll long long
using namespace std;
int n,q,pos,cnt,len;
int id[N],t[N<<1];
ll int sum,f[N];
int lowbit(int x){return x&-x;}
void modify(int x,int k){
	while(x<=N){
		t[x]+=k;
		x+=lowbit(x);
	}
}
int query(int x){
	int res=0;
	while(x>0){
		res+=t[x];
		x-=lowbit(x);
	}
	return res;
}
struct node{
	int a,b,c,tot,ans;
}num[N];
bool cmp1(node x,node y){
	if(x.c==y.c){
		return x.b<y.b;
	}
	return x.c<y.c;
}
bool cmp2(node x,node y){
	if(x.b==y.b) return x.c<y.c;
	return x.b<y.b;
}
void cdq(int l,int r){
	if(l==r) return;
	int mid=(l+r)>>1;
	cdq(l,mid); cdq(mid+1,r);
	sort(num+l,num+mid+1,cmp2);
	sort(num+mid+1,num+r+1,cmp2);
	int j=mid;
	for(int i=r;i>=mid+1;i--){
		while(j>=l && num[j].b>num[i].b){
			modify(num[j].c,1); --j;
		}
		num[i].ans+=query(N)-query(num[i].c);
	}
	for(int i=mid;i>j;i--) modify(num[i].c,-1);
	j=mid+1;
	for(int i=l;i<=mid;i++){
		while(j<=r && num[j].b<num[i].b){
			modify(num[j].c,1); ++j;
		}
		num[i].ans+=query(N)-query(num[i].c);
	}
	for(int i=mid+1;i<j;i++) modify(num[i].c,-1);
}
int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&num[i].b);
		num[i].a=i; num[i].c=n;
		id[num[i].b]=i;
	}
	for(int i=1;i<=n;i++){
		sum+=query(n)-query(num[i].b);
		modify(num[i].b,1);
	}
	for(int i=1;i<=n;i++) modify(num[i].b,-1);
	for(int i=1;i<=q;i++){
		scanf("%d",&pos);
		num[id[pos]].c=i;
	}
	cdq(1,n);
	sort(num+1,num+n+1,cmp1);
	for(int i=1;i<=q;i++) f[i]=f[i-1]+num[i].ans;
	for(int i=1;i<=q;i++) printf("%lld\n",sum-f[i-1]);
	return 0;
}
posted @ 2024-06-02 20:41  HRcohc  阅读(4)  评论(0编辑  收藏  举报