学习笔记(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;
}