CDQ分治(基础)
天使玩偶Violet
先按照时间维度分治理,然后只考虑一个点左下角的点,剩下的点旋转坐标系,把一个点转化为\(vx+vy\),就变成了在 \(vx_1<vx_2\) 且 \(vy_1<vy_2\) 的情况下求 \(vx_1+vx_2\) 最大。
我们把在 \(mid\) 左边的点的 \(op=1\) 的改成\(3\),右边同理,再按照\(x\)轴排序,这样对于一个在操作\(4\)前面的操作\(3\),他时间轴上显然早于\(4\),在\(x\)轴上显然小于\(4\),那么还剩一维\(y\)就考虑树状数组即可。
#include <bits/stdc++.h>
using namespace std;
const int N=600009;
int n,m,x,y;
struct point{
int ti,x,y,type;
}ls[N],q[N];
int pt,c[2000055],ans[N];
int lowbit(int p){return p&(-p);}
void update(int p,int x){
for(int i=p;i<=1000010;i+=lowbit(i)) c[i]=max(c[i],x);
}
void remove(int p){
for(int i=p;i<=1000010;i+=lowbit(i)){
if(c[i]!=-0x3f3f3f3f) c[i]=-0x3f3f3f3f;
else return;
}
}
int query(int p){
int res=-0x3f3f3f3f;
for(int i=p;i>=1;i-=lowbit(i)) res=max(res,c[i]);
return res;
}
void copy(){
int maxx=0,maxy=0;
pt=0;
for(int i=1;i<=n+m;i++){
if(q[i].type==2){
maxx=max(maxx,q[i].x);
maxy=max(maxy,q[i].y);
}
}
for(int i=1;i<=n+m;i++){
if(q[i].type==2 || (q[i].x<=maxx && q[i].y<=maxy))
ls[++pt]=q[i];
}
}
void cdq(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
cdq(l,mid);
cdq(mid+1,r);
int i=l;
int j=mid+1;
vector<point> vc;
while(i<=mid && j<=r){
if(ls[i].x<=ls[j].x){
if(ls[i].type==1) update(ls[i].y,ls[i].x+ls[i].y);
vc.push_back(ls[i]);
i++;
}
else{
if(ls[j].type==2) ans[ls[j].ti]=min(ans[ls[j].ti],ls[j].x+ls[j].y-query(ls[j].y));
vc.push_back(ls[j]);
j++;
}
}
while(i<=mid)
vc.push_back(ls[i]),i++;
while(j<=r){
if(ls[j].type==2){
ans[ls[j].ti]=min(ans[ls[j].ti],ls[j].x+ls[j].y-query(ls[j].y));
}
vc.push_back(ls[j]);
j++;
}
for(int i=l;i<=mid;i++){
if(ls[i].type==1)remove(ls[i].y);
}
for(int i=l;i<=r;i++) ls[i]=vc[i-l];
}
int main(){
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> x >> y;
x++,y++;q[i].ti=0,q[i].x=x,q[i].y=y,q[i].type=1;
}
for(int i=1;i<=m;i++){
int op;
cin >> op >> x >> y;
x++,y++;q[i+n].ti=i,q[i+n].x=x,q[i+n].y=y,q[i+n].type=op;
}
memset(ans,0x3f,sizeof(ans));
for(int i=1;i<=1000010;i++) c[i]=-0x3f3f3f3f;
copy(),cdq(1,pt);
for(int i=1;i<=n+m;i++) q[i].x=1000002-q[i].x;
copy(),cdq(1,pt);
for(int i=1;i<=n+m;i++) q[i].y=1000002-q[i].y;
copy(),cdq(1,pt);
for(int i=1;i<=n+m;i++) q[i].x=1000002-q[i].x;
copy(),cdq(1,pt);
for(int i=1;i<=m;i++)
if(ans[i]!=0x3f3f3f3f)
cout << ans[i] << endl;
return 0;
}
P3769 [CH弱省胡策R2]TATT
题意:四维偏序
三维偏序的 \(CDQ\) 本质就是把每个 \((x,y,z)\) 转化若干为 \((0,y,z)\) 对 \((1,y,z)\) 的贡献。剩下的就是个经典二维偏序问题。
四维偏序中珂以沿用三维偏序中的思路。
把若干 \((x,y,z,w)\) 分成 \((0/1,0/1,z,w)\),然后计算 \((0,0,z,w)\) 对 \((1,1,z,w)\)的贡献。
注意以下两点:
-
树状数组维护区间 \(max\) 是在只加点不删除点的状况下是正确的。
-
在 \(CDQ\) 时我们必须先递归计算左区间,算完左区间对右区间的贡献后在递归计算右区间,不然得出的值会变小。考虑这样一种贡献:左边对右边,右边对右边。
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n;
struct node{
int a,b,c,d,id;
int w,ans;
int grd;
}s[50009],pt[50009],ls[50009];
int l[50009];
int w[50009];
int pos1[50009];
int pos2[50009];
int tp;
bool cmp(node A,node B){
if(A.a==B.a){
if(A.b==B.b){
if(A.c==B.c)
return A.d<B.d;
return A.c<B.c;
}
return A.b<B.b;
}
return A.a<B.a;
}
bool cmp2(node A,node B){
if(A.b==B.b){
if(A.a==B.a){
if(A.c==B.c)
return A.d<B.d;
return A.c<B.c;
}
return A.a<B.a;
}
return A.b<B.b;
}
bool cmp3(node A,node B){
if(A.c==B.c){
return A.d<B.d;
}
return A.c<B.c;
}
int t[50009];
int lowbit(int p){return p&(-p);}
void add(int p,int delta){
for(int i=p;i<=n;i+=lowbit(i)){
t[i]=max(t[i],delta);
}
}
int query(int p){
int res=0;
for(int i=p;i>=1;i-=lowbit(i)){
res=max(res,t[i]);
}
return res;
}
void del(int p){
for(int i=p;i<=n;i+=lowbit(i))
t[i]=0;
}
int ans[50009];
void CDQ2(int l,int r){
//a轴乱序,b轴排序
if(l==r) return;
int mid=(l+r)>>1;
CDQ2(l,mid);
sort(pt+l,pt+mid+1,cmp3);
sort(pt+mid+1,pt+r+1,cmp3);
int L=l;
for(int i=mid+1;i<=r;i++){
while(L<=mid && pt[i].c>=pt[L].c){
if(pt[L].grd==0){add(pt[L].d,pt[L].ans);}
L++;
}
if(pt[i].grd==1)
pt[i].ans=max(pt[i].ans,pt[i].w+query(pt[i].d));
}
for(int i=l;i<=L;i++){
if(pt[i].grd==0) del(pt[i].d);
}
for(int i=l;i<=r;i++)
ls[pos2[pt[i].id]]=pt[i];
for(int i=l;i<=r;i++)
pt[i]=ls[i];
CDQ2(mid+1,r);
}
void CDQ1(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ1(l,mid);
for(int i=l;i<=mid;i++)
pt[i].grd=0;
for(int i=mid+1;i<=r;i++)
pt[i].grd=1;
sort(pt+l,pt+r+1,cmp2);
for(int i=l;i<=r;i++)
pos2[pt[i].id]=i;
CDQ2(l,r);
for(int i=l;i<=r;i++){
ls[pos1[pt[i].id]]=pt[i];
}
for(int i=l;i<=r;i++)
pt[i]=ls[i];
CDQ1(mid+1,r);
return;
}
signed main(){
cin >> n;
for(int i=1;i<=n;i++){
cin >> s[i].a >> s[i].b >> s[i].c >> s[i].d;
l[i]=s[i].d;
s[i].id=i;
s[i].w=1;
}
sort(l+1,l+n+1);
int len=unique(l+1,l+n+1)-l-1;
for(int i=1;i<=n;i++){
s[i].d=lower_bound(l+1,l+len+1,s[i].d)-l;
}
sort(s+1,s+n+1,cmp);
for(int i=1;i<=n;i++){
if(s[i].a==s[i-1].a && s[i].b==s[i-1].b && s[i].c==s[i-1].c && s[i].d==s[i-1].d)
pt[tp].w+=s[i].w;
else{
pt[++tp]=s[i];
}
}
sort(pt+1,pt+tp+1,cmp);
for(int i=1;i<=tp;i++){
pt[i].id=i;
pt[i].ans=pt[i].w;
pos1[pt[i].id]=i;
}
CDQ1(1,tp);
int maxx=0;
for(int i=1;i<=n;i++){
maxx=max(maxx,pt[i].ans);
}
cout << maxx << endl;
system("pause");
return 0;
}
CF1045G
首先按照\(r_i\)降序排序,这样只用右边能看到左边就可以了。
再来考虑排完序后的 \(CDQ\) ,乍一看可以左右分别按照 \(x_i\) 排序,然后归并,然后每一次贡献都是一个前缀。
其实这样是不正确的,因为此时 \(r_i\) 是乱序,可能在往右扫的过程中前缀是反复横跳状态,而归并只能从左往右,就寄了。
那么只好考虑以 \(q_i\) 排序了,此时的状态是:\(r_i\)乱序,但所有左边的 \(r_i\) 一定大于右边的 \(r_i\),然后左半边和右半边的 \(q_i\) 是分别排好序的。
那么对于右边的点,左边的可能可以贡献给他的点集就是一段连续的区间,这里可以发现,这个区间的端点一定是单调递增的,那么就可以愉快的双指针了。
还差一维,就是刚刚的\(x_i\)这一维度,可以考虑树状数组,就解决了。
注意要离散化。
CF848C
绷不住了,竟然有二维分块做法,万万没想到。
如果用\(CDQ\)分治,那么首先这个贡献是需要差分的,考虑转换一下,变成求:
那么我们可以把每一个 \(i\) 看作是一个横坐标是 \(i\) 纵坐标是 \(pre_{A_i}\) 的点,那么查询就是查询一个左上角矩形了。当然还要加上时间轴。
具体来说也就是:
-
\(i<=R\)
-
\(pre_{A_i}>=L\)
-
\(T[q[lr]]>T[pt]\)
-
每个点的贡献是 \(i-pre[a_i]\)
三位偏序了,但是修改有点麻烦,可以考虑抵消上一次的点再加入新点。
然后再来考虑怎么三位偏序,首先按照时间维度排序,再把左右分别按照 \(i\) 排序,然后再按照 \(i\) 归并一下,如果当前是左半边的 \(i\) ,就在树状数组的 \(pre_{A_i}\) 位置上加 \(i-pre_{A_i}\)。对于右半部分的一个询问,当前已经加入的节点的 \(i\)值比他小满足条件 \(1\),第二维就是一段树状数组的后缀查询求和。
#include <bits/stdc++.h>
using namespace std;
const int N=100009;
int n,m;
int a[N],pre[N],nxt[N];
set<int> S[N];
struct node{
int x,prex,op,w;
int b,c;
int id;
}e[N*2];
int tp;
int bit[N];
int lowbit(int p){return p&(-p);}
void add(int p,int delta){
for(int i=p;i<=n;i+=lowbit(i))
bit[i]+=delta;
}
void clean(int p){
for(int i=p;i<=n;i+=lowbit(i))
bit[i]=0;
}
int query(int l,int r){
int resr=0;
for(int i=r;i<=n;i+=lowbit(i))
resr+=bit[i];
int resl=0;
l--;
for(int i=l;i<=n;i+=lowbit(i))
resl+=bit[i];
return resr-resl;
}
bool cmp1(node A,node B){
if(A.op!=B.op) return A.op<B.op;
if(A.op==1) return A.x<B.x;
else return A.c<B.c;
//return A.x<B.x;
}
int ans[N];
void change(int pla,int v){
e[++tp].op=1;
e[tp].x=pla;
e[tp].prex=pre[pla];
e[tp].w=pre[pla]-pla;
pre[pla]=v;
e[++tp].op=1;
e[tp].x=pla;
e[tp].prex=pre[pla];
e[tp].w=pla-pre[pla];
}
void CDQ(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
CDQ(l,mid);
CDQ(mid+1,r);
//sort(e+l,e+mid+1,cmp1);
//sort(e+mid+1,e+r,cmp1);
int L=l;
for(int i=mid+1;i<=r;i++){
if(e[i].op!=2) continue;
while(L<=mid && (e[i].x>=e[L].x || e[i].op==2)){
if(e[i].op==1){
add(e[i].prex,e[i].w);
}
L++;
}
ans[e[i].id]+=query(e[i].b,n);
}
for(int i=l;i<=L;i++)
add(e[i].prex,-e[i].w);
sort(e+l,e+r+1,cmp1);
}
int main(){
cin >> n >> m;
for(int i=1;i<=n;i++){
cin >> a[i];
set<int>::iterator it=S[a[i]].lower_bound(i);
if(it!=S[a[i]].end()) pre[i]=*it;
set<int>::iterator it2=S[a[i]].upper_bound(i);
if(it2!=S[a[i]].end()) nxt[i]=*it2;
S[a[i]].insert(i);
e[++tp].x=i,e[tp].prex=pre[i],e[tp].w=i-pre[i];
e[tp].op=1;
//e[tp].id=tp;不需要还原
}
for(int i=1;i<=m;i++){
cin >> e[++tp].op >> e[tp].b >> e[tp].c;
//e[tp].id=tp;不需要还原
if(e[tp].op==1){
if(e[tp].c==a[e[tp].b]) continue;
int PP,NN;
set<int>::iterator it=S[a[i]].lower_bound(i);
if(it!=S[a[i]].end()) PP=*it;
set<int>::iterator it2=S[a[i]].upper_bound(i);
if(it!=S[a[i]].end()) NN=*it2;
S[a[e[tp].b]].erase(e[tp].b);
S[e[tp].c].insert(e[tp].b);
nxt[pre[e[tp].b]]=nxt[e[tp].b];
nxt[PP]=e[tp].b;
change(e[tp].b,PP);
nxt[e[tp].b]=NN;
}
}
CDQ(1,tp);
for(int i=1;i<=tp;i++){
if(ans[i]){
cout << ans[i] << endl;
}
}
system("pause");
return 0;
}
P4207 [NOI2007] 货币兑换
首先有一个理论上应该一眼的性质:要么不买,要么花光积蓄。
证明考虑如果我们买入部分,那么卖出的时候赚的钱就少了。
这也提示我们类似这种每一次可以选择部分物品操作的题目,要思考思考有没有必要部分!
那么我们每天的状态就是两个:
- 身无分文,全是金券。
- 一夜暴富,没有金券。
我们设 \(f_i\) 为假如在 \(i\) 这一天没有金权最多能得到的钱。
那么我们转移可以从 \(f_{i-1}\) (什么也不干),或者 \(f_j\) (我在\(j\)这一天买然后在 \(i\)这一天卖出)而来。
\(x_i=f_i\times \frac{R_i}{A_iR_i+B_i}\)
\(y_i=f_i\times \frac{1}{A_iR_i+B_i}\)
\(f_i=max\) {\(f_{i-1},max\) { \(x_jA_i+y_jB_i\) } }
后面的一堆似乎很斜率优化,于是考虑 \(f_j\) 什么时候优于 \(f_k\)。
\(\frac{y_j-y_k}{x_j-x_k} \gt -\frac{A_i}{B_i}\)
由于正负号问题,所以不能普通的斜率优化,所以考虑斜率优化,考虑用 \(CDQ\)分治,这样就能根据 \(x\)轴排序了,就能在左半边建立上凸壳再转移到右边了。
BZOJ2961 共点园
咕咕。

浙公网安备 33010602011771号