这篇文章上次修改于 281 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
CDQ 分治
思路总结
考虑三维偏序问题,也就是给定有 $n$ 个元素的序列 $a,b,c$ ,要求出有多少对 $i,j$ 满足 $a_i \leq a_j,b_i \leq b_j , c_i \leq c_j$
我们先按 $a$ 为关键字排序,把问题转换为是否存在 $i \leq j ,b_i \leq b_j ,c_i \leq c_j$ 。
我们继续考虑消掉 $b$ 这一维,如果只剩 $c$ 这一维,则可以套高级数据结构直接求解。
经典的排序算法中,可以统计类似逆序对的算法有归并排序,我们考虑采用归并排序的办法,在对 $b$ 进行排序时,统计答案。
归并排序的特点是先左右再合在一起,我们考虑在合并时统计跨越两个区间的答案。
具体的,solve(i,j)
的过程是先分治为两个相等的区间,然后统计跨这两个区间的答案。
我们在归并排序的过程中,即可统计三维偏序问题答案。
先按 $a$ 排序,然后归并排序的过程中以 $b$ 为关键字,实际上我们在归并排序的并的过程中,是按照 $b$ 的大小进行合并的,所以简单的把 $c$ 放进树状数组查询所有已经加入的数小于 $c$ 的元素即可统计答案。
另外,这种方法统计答案实际上统计了对于每个 $i$ 的答案。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
inline int read();
class node{
public:
int a,b,c;
int cnt;
int i;
node(int aa=0,int bb=0,int cc=0){
a=aa;
b=bb;
c=cc;
return ;
}
}b[1000005],a[1000005],tem[1000005];
int tree[200005];
int cnt2[1000005];
int end[1000005];
int f[1000005];
int lowbit(int a){
return a&-a;
}
void add(int a,int i){
while(a<=200000){
tree[a]+=i;
a+=lowbit(a);
}
return ;
}
int query(int a){
int ans=0;
while(a>0){
ans+=tree[a];
a-=lowbit(a);
}
return ans;
}
int ans[1000005];
bool cmp(node a,node b){
if(a.a!=b.a){
return a.a<b.a;
}
if(a.b!=b.b){
return a.b<b.b;
}
return a.c<b.c;
}
void cdq(int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
cdq(mid+1,r);
int ii1=l,ii2=mid+1;
int da=l-1;
while(ii1<=mid&&ii2<=r){
if(a[ii1].b<=a[ii2].b){
add(a[ii1].c,a[ii1].cnt);
tem[++da]=a[ii1];
ii1++;
}else{
ans[a[ii2].i]+=query(a[ii2].c);
tem[++da]=a[ii2];
ii2++;
}
}
while(ii1<=mid){
add(a[ii1].c,a[ii1].cnt);
tem[++da]=a[ii1];
ii1++;
}
while(ii2<=r){
ans[a[ii2].i]+=query(a[ii2].c);
tem[++da]=a[ii2];
ii2++;
}
for(int i=l;i<=mid;i++){
add(a[i].c,-a[i].cnt);
}
for(int i=l;i<=r;i++){
a[i]=tem[i];
}
return ;
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read(),k;
k=read();
for(int i=1;i<=n;i++){
b[i].a=read();
b[i].b=read();
b[i].c=read();
}
std::sort(b+1,b+n+1,cmp);
int cnt=0;
int tot=0;
for(int i=1;i<=n;i++){
if(b[i].a!=b[i+1].a||b[i].b!=b[i+1].b||b[i].c!=b[i+1].c){
cnt++;
a[++tot]=b[i];
end[i]=tot;
cnt2[i]=cnt;
a[tot].cnt=cnt;
a[tot].i=i;
cnt=0;
}else{
cnt++;
}
}
cdq(1,tot);
for(int i=1;i<=n;i++){
if(end[i])
f[ans[i]+cnt2[i]-1]+=cnt2[i];
}
for(int i=0;i<=n-1;i++){
printf("%d\n",f[i]);
}
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
题目选做
二/三维偏序
CDQ分治可用于离线求解偏序问题,故可以将其他问题转换为三维偏序问题。
很多在线问题可以转换为离线问题因为我们可以把时间搞成一维,问题完美解决,另外实际上CDQ分治每个点可以附加权值。
P2717寒假作业
平均值大于问题全部减去 $k$ 转换为区间和大于 $0$ 问题。
解1
前缀和后统计逆序对。
解2
并时维护前缀以及后缀和,处理出区间数量即可。
此题较为模板,实际上都是利用了CDQ的原理排序来解决问题。
P2163 园丁的烦恼
离线询问坐标系中任意矩阵的元素个数。
元素个数、询问个数小于 $5 \times 10^5$ ,坐标非负且小于 $10^7$ 。
很明显要利用前缀和,但是数据范围即使离散化后也很难做,我们考虑把询问离线化,这样我们的问题全部转换为形似 $[0,0]-[l,r]$ 的矩阵的问题,之后就是一个简单的三维偏序问题。
这里要注意哪些能够统计进答案,哪些不行。
P3157 动态逆序对
满足要求的有两种情况:$val_i \leq val_j,time_i \leq time_j , pos_i \geq pos_j$ 或者 $time_i \leq time_j,val_i \geq val_j,pos_i \leq pos_j$。
三维偏序时处理两次即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#define int long long
inline int read();
int pos[200005];
int tree[200005];
int lowbit(int x){
return x&(-x);
}
void mod(int x,int y){
while(x<=200000){
tree[x]+=y;
x+=lowbit(x);
}
return ;
}
int query(int x){
int ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
class node{
public:
int tim,a,opp,pos;
node(int x=0,int y=0,int z=0,int aa=0){
tim=x;
a=y;
opp=z;
pos=aa;
}
}seq[200005],temp[200005];
int ans[200005];
void cdq(int l,int r){
if(l==r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
cdq(mid+1,r);
int nl=l;
int nr=mid+1;
int nw=l;
while(nl<=mid&&nr<=r){
if(seq[nl].pos<=seq[nr].pos){
temp[nw++]=seq[nl];
mod(seq[nl].a,seq[nl].opp);
nl++;
}else{
temp[nw++]=seq[nr];
ans[seq[nr].tim]+=seq[nr].opp*(query(200000)-query(seq[nr].a));
nr++;
}
}
while(nl<=mid){
temp[nw++]=seq[nl];
mod(seq[nl].a,seq[nl].opp);
nl++;
}
while(nr<=r){
temp[nw++]=seq[nr];
ans[seq[nr].tim]+=seq[nr].opp*(query(200000)-query(seq[nr].a));
nr++;
}
for(int i=l;i<=mid;i++){
mod(seq[i].a,-seq[i].opp);
}
nl=mid;
nr=r;
while(nl>=l&&nr>=mid+1){
if(seq[nl].pos>=seq[nr].pos){
mod(seq[nl].a,seq[nl].opp);
nl--;
}else{
ans[seq[nr].tim]+=seq[nr].opp*(query(seq[nr].a-1));
nr--;
}
}
while(nl>=l){
mod(seq[nl].a,seq[nl].opp);
nl--;
}
while(nr>=mid+1){
ans[seq[nr].tim]+=seq[nr].opp*(query(seq[nr].a-1));
nr--;
}
for(int i=l;i<=mid;i++){
mod(seq[i].a,-seq[i].opp);
}
for(int i=l;i<=r;i++){
seq[i]=temp[i];
}
return ;
}
signed main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read(),m;
m=read();
for(int i=1;i<=n;i++){
seq[i].a=read();
seq[i].tim=0;
seq[i].opp=1;
seq[i].pos=i;
pos[seq[i].a]=i;
}
for(int i=1;i<=m;i++){
int m=read();
seq[i+n].a=m;
seq[i+n].tim=i;
seq[i+n].opp=-1;
seq[i+n].pos=pos[m];
}
cdq(1,n+m);
for(int i=1;i<=m;i++){
ans[i]+=ans[i-1];
}
for(int i=0;i<m;i++){
printf("%lld\n",ans[i]);
}
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
P4169 天使玩偶/SJY摆棋子
初始给定 $n$ 个点,动态加点并且查询距离某点最近的点的距离。
CDQ分治,搞一维时间,然后考虑转换为三维偏序,我们只考虑所有 $x_i \leq x_j$ 和 $y_i \leq y_j$ 的点对,其他点对可以旋转坐标系得到,就是一个简单三维偏序。
实测不需要卡常,你们就是人傻常数大。
最大时间啊2.03s稳过,根本不怕。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
inline int read();
class node{
public:
int x,y,t,quan,i;
}pos[600005],temp[600005];
int ans[600005];
bool cmp1(node a,node b){
if(a.x!=b.x){
return a.x<b.x;
}
if(a.y!=b.y){
return a.y<b.y;
}
return a.t<b.t;
}
int tree[1000015];
int lowbit(int x){
return (x)&(-x);
}
void add(int x,int y){
while(x<=1000005){
tree[x]=std::max(tree[x],y);
x+=lowbit(x);
}
return ;
}
int query(int x){
int ans=-0x3f3f3f3f;
while(x>0){
ans=std::max(ans,tree[x]);
x-=lowbit(x);
}
return ans;
}
void clear(int x){
while(x<=1000005){
tree[x]=-0x3f3f3f3f;
x+=lowbit(x);
}
return ;
}
void cdq(int l,int r){
if(l==r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
cdq(mid+1,r);
int nl=l,nr=mid+1;
int now=l-1;
while(nl<=mid&&nr<=r){
if(pos[nl].y<=pos[nr].y){
temp[++now]=pos[nl];
if(pos[nl].quan){
add(pos[nl].t,pos[nl].x+pos[nl].y);
}
nl++;
}else{
temp[++now]=pos[nr];
ans[pos[nr].i]=std::min(ans[pos[nr].i],pos[nr].x+pos[nr].y-query(pos[nr].t));
nr++;
}
}
while(nl<=mid){
temp[++now]=pos[nl];
if(pos[nl].quan){
add(pos[nl].t,pos[nl].x+pos[nr].y);
}
nl++;
}
while(nr<=r){
temp[++now]=pos[nr];
ans[pos[nr].i]=std::min(ans[pos[nr].i],pos[nr].x+pos[nr].y-query(pos[nr].t));
nr++;
}
for(int i=l;i<=mid;i++){
clear(pos[i].t);
}
for(int i=l;i<=r;i++){
pos[i]=temp[i];
}
return ;
}
std::queue<int>qu;
int main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read(),m;
m=read();
for(int i=1;i<=n;i++){
pos[i].x=read()+1;
pos[i].y=read()+1;
pos[i].t=1;
pos[i].quan=1;
pos[i].i=i;
ans[i]=0x3f3f3f3f;
}
for(int i=1;i<=1000005;i++){
tree[i]=-0x3f3f3f3f;
}
for(int i=1;i<=m;i++){
int t=read();
ans[i+n]=0x3f3f3f3f;
switch(t){
case 1:{
pos[i+n].x=read()+1;
pos[i+n].y=read()+1;
pos[i+n].t=i+1;
pos[i+n].quan=1;
pos[i+n].i=i+n;
break;
}
case 2:{
pos[i+n].x=read()+1;
pos[i+n].y=read()+1;
pos[i+n].t=i+1;
pos[i+n].i=i+n;
qu.push(i+n);
pos[i+n].quan=0;
}
}
}
std::sort(pos+1,pos+n+m+1,cmp1);
cdq(1,n+m);
for(int i=1;i<=n+m;i++){
pos[i].y=1000005-pos[i].y;
}
std::sort(pos+1,pos+n+m+1,cmp1);
cdq(1,n+m);
for(int i=1;i<=n+m;i++){
pos[i].x=1000005-pos[i].x;
}
std::sort(pos+1,pos+n+m+1,cmp1);
cdq(1,n+m);
for(int i=1;i<=n+m;i++){
pos[i].y=1000005-pos[i].y;
}
std::sort(pos+1,pos+n+m+1,cmp1);
cdq(1,n+m);
while(qu.size()>0){
printf("%d\n",ans[qu.front()]);
qu.pop();
}
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
P4390 Mokia 摩基亚
动态加点并且询问矩阵元素和。
CDQ分治在处理重复元素的时候会出错,但是这道题很明显同一个点只有一个对答案产生贡献,适当重排顺序把可以贡献的放前面即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
inline int read();
long long ans[2000005];
class node{
public:
int x,y,num,i;
node(int a=0,int b=0,int c=0,int d=0){
x=a;
y=b;
num=c;
i=d;
}
}seq[2000005],temp[2000005];
int qx1[2000005],qx2[2000005],qy1[2000005],qy2[2000005];
long long tree[2000005];
int lowbit(int x){
return x&(-x);
}
void mod(int x,int y){
while(x<=2000000){
tree[x]+=y;
x+=lowbit(x);
}
return ;
}
int query(int x){
int ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
bool cmp(node a,node b){
if(a.x!=b.x){
return a.x<b.x;
}
if(a.y!=b.y){
return a.y<b.y;
}
if(a.i!=b.i){
return a.i<b.i;
}
return a.num>b.num;
}
void cdq(int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
cdq(mid+1,r);
int nl=l,nr=mid+1;
int now=l;
while(nl<=mid&&nr<=r){
if(seq[nl].y<=seq[nr].y){
temp[now++]=seq[nl];
mod(seq[nl].i,seq[nl].num);
nl++;
}else{
temp[now++]=seq[nr];
ans[seq[nr].i]+=query(seq[nr].i);
nr++;
}
}
while(nl<=mid){
temp[now++]=seq[nl];
mod(seq[nl].i,seq[nl].num);
nl++;
}
while(nr<=r){
temp[now++]=seq[nr];
ans[seq[nr].i]+=query(seq[nr].i);
nr++;
}
for(int i=l;i<=mid;i++){
mod(seq[i].i,-seq[i].num);
}
for(int i=l;i<=r;i++){
seq[i]=temp[i];
}
return ;
}
signed main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int op=read();
int w;
int now=0;
int qq=0;
while(op!=3){
if(op==0){
w=read();
}else if(op==1){
seq[++now].x=read();
seq[now].y=read();
seq[now].num=read();
seq[now].i=now;
}else{
int x1=read();
int y1=read();
int x2=read();
int y2=read();
now++;
seq[now]=node(x2,y2,0,now);
qx1[++qq]=now;
now++;
seq[now]=node(x1-1,y2,0,now);
qx2[qq]=now;
now++;
seq[now]=node(x2,y1-1,0,now);
qy1[qq]=now;
now++;
seq[now]=node(x1-1,y1-1,0,now);
qy2[qq]=now;
}
op=read();
}
std::sort(seq+1,seq+now+1,cmp);
cdq(1,now);
for(int i=1;i<=qq;i++){
printf("%lld\n",ans[qx1[i]]-ans[qx2[i]]-ans[qy1[i]]+ans[qy2[i]]);
}
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
P3658 Why Did the Cow Cross the Road III P
给定两个排列 $a,b$ ,求出所有数对$(i,j)$满足 $a_i \leq a_j , b_j \leq b_i , |i-j| > k$ 的数量,同样是三维偏序关系,统计答案时我们仍然可以发现 $|i-j| > k$ 的条件实际上就是值域 $[0,i-k-1] \cup [i+k+1,\infty]$ 的答案,树状数组仍然可以做。
读入方式有一点奇怪,请参考代码。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define int long long
inline int read();
class node{
public:
int a,b,i;
node(int x=0,int y=0,int z=0){
a=x;
b=y;
i=z;
return ;
}
}seq[100005],temp[100005];
bool operator < (node a,node b){
if(a.a!=b.a){
return a.a<b.a;
}
return a.b>b.b;
}
int tree[100005];
int lowbit(int x){
return x&(-x);
}
void add(int x,int y){
while(x<=100000){
tree[x]+=y;
x+=lowbit(x);
}
return ;
}
int query(int x){
int ans=0;
while(x>0){
ans+=tree[x];
x-=lowbit(x);
}
return ans;
}
int ans=0;
int k;
void cdq(int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
cdq(mid+1,r);
int nl=l,nr=mid+1;
int now=l-1;
while(nl<=mid&&nr<=r){
if(seq[nl].b>=seq[nr].b){
temp[++now]=seq[nl];
add(seq[nl].i,1);
nl++;
}else{
temp[++now]=seq[nr];
ans+=(query(100000)-(query(std::min(seq[nr].i+k,100000ll))-query(std::max(seq[nr].i-k-1,0ll))));
nr++;
}
}
while(nl<=mid){
temp[++now]=seq[nl];
add(seq[nl].i,1);
nl++;
}
while(nr<=r){
temp[++now]=seq[nr];
ans+=(query(100000)-(query(std::min(seq[nr].i+k,100000ll))-query(std::max(seq[nr].i-k-1,0ll))));
nr++;
}
for(int i=l;i<=mid;i++){
add(seq[i].i,-1);
}
for(int i=l;i<=r;i++){
seq[i]=temp[i];
}
return ;
}
signed main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read();
k=read();
for(int i=1;i<=n;i++){
int x=read();
seq[x].a=i;
}
for(int i=1;i<=n;i++){
int y=read();
seq[y].b=i;
seq[y].i=y;
}
std::sort(seq+1,seq+n+1);
cdq(1,n);
printf("%lld",ans);
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
DP转移
DP转移的条件非常像偏序问题的,同样可以CDQ分治。
具体思路是先 solve(l,mid)
,然后处理中间的,然后 solve(mid+1,r)
。
部分问题的转移只能从前面转移到后面,那CDQ分治中间处理完之后还要恢复到排序之前的状态。
P4093 序列
$dp_i=\max\limits_{j<i}\{dp_j+1\}(mx_j\leq a_i,a_j\leq mn_j)$ 。
条件非常的偏序,直接套CDQ分治即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
inline int read();
class node{
public:
int seq,mx,mn,i;
node(int a=0,int b=0,int c=0,int t=0){
seq=a;
mx=b;
mn=c;
i=t;
return ;
}
}seq[100005];
bool cmp1(node a,node b){
return a.mx<b.mx;
}
bool cmp2(node a,node b){
return a.seq<b.seq;
}
bool cmp3(node a,node b){
return a.i<b.i;
}
int tree[100005];
int lowbit(int x){
return x&(-x);
}
int query(int x){
int ans=-0x3f3f3f3f;
while(x>0){
ans=std::max(ans,tree[x]);
x-=lowbit(x);
}
return ans;
}
void mod(int x,int y){
while(x<=100000){
tree[x]=std::max(tree[x],y);
x+=lowbit(x);
}
return ;
}
void clear(int x){
while(x<=100000){
tree[x]=-0x3f3f3f3f;
x+=lowbit(x);
}
return ;
}
int dp[100005];
void cdq(int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
std::sort(seq+l,seq+mid+1,cmp1);
std::sort(seq+mid+1,seq+r+1,cmp2);
int nl=l,nr=mid+1;
while(nl<=mid&&nr<=r){
if(seq[nl].mx<=seq[nr].seq){
mod(seq[nl].seq,dp[seq[nl].i]+1);
nl++;
}else{
dp[seq[nr].i]=std::max(dp[seq[nr].i],query(seq[nr].mn));
nr++;
}
}
while(nl<=mid){
mod(seq[nl].seq,dp[seq[nl].i]+1);
nl++;
}
while(nr<=r){
dp[seq[nr].i]=std::max(dp[seq[nr].i],query(seq[nr].mn));
nr++;
}
for(int i=l;i<=mid;i++){
clear(seq[i].seq);
}
std::sort(seq+l,seq+r+1,cmp3);
cdq(mid+1,r);
return ;
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read(),m;
m=read();
for(int i=1;i<=100000;i++){
tree[i]=-0x3f3f3f3f;
}
for(int i=1;i<=n;i++){
dp[i]=1;
seq[i].seq=seq[i].mx=seq[i].mn=read();
seq[i].i=i;
}
for(int i=1;i<=m;i++){
int x=read();
int y=read();
seq[x].mx=std::max(seq[x].mx,y);
seq[x].mn=std::min(seq[x].mn,y);
}
cdq(1,n);
int ans=-0x3f3f3f3f;
for(int i=1;i<=n;i++){
ans=std::max(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
P3364 Cool loves touli
可以一眼瞪出按照等级排序之后只用 $dp_i=\max\limits_{j<i} \{dp_j+1\}$ 即可,其中 $j$ 的限制就是题目中那坨不大于不小于,而这是偏序关系,CDQ分治优化转移即可。
#include <iostream>
#include <cstdio>
#include <algorithm>
inline int read();
class node{
public:
int l,s,w,a;
int i;
node(int xx=0,int yy=0,int zz=0,int aa=0,int ii=0){
l=xx;
s=yy;
w=zz;
a=aa;
i=ii;
return ;
}
}seq[100005];
bool cmp1(node a,node b){
return a.a<b.a;
}
bool cmp2(node a,node b){
return a.s<b.s;
}
bool cmp3(node a,node b){
return a.i<b.i;
}
bool cmp4(node a,node b){
return a.l<b.l;
}
int tree[1000005];
int lowbit(int x){
return x&(-x);
}
int query(int x){
int ans=-0x3f3f3f3f;
while(x>0){
ans=std::max(ans,tree[x]);
x-=lowbit(x);
}
return ans;
}
void mod(int x,int y){
while(x<=1000000){
tree[x]=std::max(tree[x],y);
x+=lowbit(x);
}
return ;
}
void clear(int x){
while(x<=1000000){
tree[x]=-0x3f3f3f3f;
x+=lowbit(x);
}
return ;
}
int dp[1000005];
void cdq(int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
std::sort(seq+l,seq+mid+1,cmp1);
std::sort(seq+mid+1,seq+r+1,cmp2);
int nl=l,nr=mid+1;
while(nl<=mid&&nr<=r){
if(seq[nl].a<=seq[nr].s){
mod(seq[nl].w,dp[seq[nl].i]);
nl++;
}else{
dp[seq[nr].i]=std::max(dp[seq[nr].i],query(seq[nr].a)+1);
nr++;
}
}
while(nl<=mid){
mod(seq[nl].w,dp[seq[nl].i]);
nl++;
}
while(nr<=r){
dp[seq[nr].i]=std::max(dp[seq[nr].i],query(seq[nr].a)+1);
nr++;
}
for(int i=l;i<=mid;i++){
clear(seq[i].w);
}
std::sort(seq+l,seq+r+1,cmp4);
cdq(mid+1,r);
return ;
}
int X[1000005];
int main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read();
int m=0;
for(int i=1;i<=n;i++){
dp[i]=1;
seq[i].l=read();
X[++m]=seq[i].l;
seq[i].s=read();
X[++m]=seq[i].s;
seq[i].w=read();
X[++m]=seq[i].w;
seq[i].a=read();
X[++m]=seq[i].a;
seq[i].i=i;
}
std::sort(X+1,X+m+1);
for(int i=1;i<=n;i++){
seq[i].l=std::lower_bound(X+1,X+m+1,seq[i].l)-X;
seq[i].s=std::lower_bound(X+1,X+m+1,seq[i].s)-X;
seq[i].w=std::lower_bound(X+1,X+m+1,seq[i].w)-X;
seq[i].a=std::lower_bound(X+1,X+m+1,seq[i].a)-X;
}
std::sort(seq+1,seq+n+1,cmp4);
cdq(1,n);
int ans=-0x3f3f3f3f;
for(int i=1;i<=n;i++){
ans=std::max(ans,dp[i]);
}
printf("%d",ans);
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
tips: 请注意题目是否真的满足偏序关系,只有像这道题仅要求相邻的元素满足偏序关系才行,这是因为偏序关系没有传递性。
斜率优化/凸包维护
cdq分治的形式可以让我们维护很多DP转移,其中最经典的就是斜率优化,另外实际上这样的方式本质维护了凸包。
P2497 基站建设
可以发现,转移式为 $dp_i=\min\{dp_j+\frac {x_i-x_j} {2 \sqrt{r_j}}\}+v_i$ 。
整个式子非常的斜率优化,所有我们分离一下相关的函数。
做毛线斜率优化,nm x 不单调
这样的凸包实际上维护方法很多,经典的办法有各路高级数据结构,当然也有CDQ分治。
原理很好理解,直接转移我们不能排序保证 $x$ 的单调性,实际上这个问题和三维偏序中为什么不能直接计算的原因很类似,而CDQ分治在分治的每一层只处理部分转移从而允许进行排序。形式和DP转移一样。
先是 solve(l,mid)
,然后进行排序, 先维护 [l,mid]
的凸包,这里排序后直接使用单调栈进行维护,然后再 solve(mid+1,r)
。
请注意,和维护DP转移一样,你可能需要处理完后按照最开始的下标再排一边序。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <deque>
#define int long long
#define double long double
inline int read();
class node{
public:
int x,r,v,a;
double dp;
node(int aa=0,int b=0,int c=0,int d=0,double e=0){
x=aa;
r=b;
v=c;
a=d;
dp=e;
}
};
int x[500005];
int r[500005];
int v[500005];
int a[500005];
node temp[500005];
double dp[500005];
int qu[2000005];
int tail;
double X(int i){
// return 1.0/(2*std::sqrt(r[i]));
return std::sqrt(r[i])/r[i]/2.0;
}
double Y(int i){
return -dp[i]+((double)(x[i]))/(2*std::sqrt(r[i]));
}
double K(int i){
return x[i];
}
double B(int i){
return v[i];
}
bool cmp(int i,int j,int i2,int j2){
// return (Y(i)-Y(j))/(X(i)-X(j))<(Y(i2)-Y(j2))/(X(i2)-X(j2));
return (Y(i)-Y(j))*(X(i2)-X(j2))>(Y(i2)-Y(j2))*(X(i)-X(j));
}
void cdq(int l,int r){
if(l>=r){
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
tail=0;
for(int i=l;i<=mid;i++){
while(tail>1&&cmp(i,qu[tail],qu[tail],qu[tail-1])){
tail--;
}
qu[++tail]=i;
}
for(int i=mid+1;i<=r;i++){
while(tail>1&&x[i]*(X(qu[tail])-X(qu[tail-1]))>(Y(qu[tail])-Y(qu[tail-1]))){
tail--;
}
int j=qu[tail];
dp[i]=std::min(dp[i],(dp[j]+v[i]+((double)(x[i]-x[j])/(2.0*std::sqrt(::r[j])))));
}
cdq(mid+1,r);
int l1=l,l2=mid+1;
int now=l-1;
while(l1<=mid&&l2<=r){
if(a[l1]>=a[l2]){
temp[++now].a=a[l1];
temp[now].r=::r[l1];
temp[now].v=v[l1];
temp[now].x=x[l1];
temp[now].dp=dp[l1];
l1++;
}else{
temp[++now].a=a[l2];
temp[now].r=::r[l2];
temp[now].v=v[l2];
temp[now].x=x[l2];
temp[now].dp=dp[l2];
l2++;
}
}
while(l1<=mid){
temp[++now].a=a[l1];
temp[now].r=::r[l1];
temp[now].v=v[l1];
temp[now].x=x[l1];
temp[now].dp=dp[l1];
l1++;
}
while(l2<=r){
temp[++now].a=a[l2];
temp[now].r=::r[l2];
temp[now].v=v[l2];
temp[now].x=x[l2];
temp[now].dp=dp[l2];
l2++;
}
for(int i=l;i<=r;i++){
a[i]=temp[i].a;
x[i]=temp[i].x;
::r[i]=temp[i].r;
v[i]=temp[i].v;
dp[i]=temp[i].dp;
}
}
signed main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read(),m;
m=read();
for(int i=1;i<=n;i++){
x[i]=read();
r[i]=read();
v[i]=read();
a[i]=r[i];
dp[i]=0x3f3f3f3f3f3f3f3f;
}
dp[1]=v[1];
cdq(1,n);
double ans=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=n;i++){
if(r[i]+x[i]>=m){
ans=std::min(ans,dp[i]);
}
}
printf("%.3Lf",ans);
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/
P4027 货币兑换
先瞪DP式,我们设 $dp_i$ 表示第 $i$ 天所有的钱数的最大值,我们的dp式实际上非常简单,朴素转移直接枚举上一次买入的时间即可。
好看吗?反正 x 还是不单调 。
继续CDQ分治,如法炮制即可,但是注意除开上面那样DP,我们也可以直接从 $dp_{i-1}$ 转移到 $dp_i$ ,我们只用在CDQ递归到只有一个点的区间的时候处理即可。
如果有 $x_i=x_j$ 的情况出现,我们可认为斜率为 $\infty$ ,但是你需要特判因为你会发现有负数这个东西。
#include <iostream>
#include <cstdio>
#include <algorithm>
#define double long double
inline int read();
double a[100005];
double b[100005];
double rate[100005];
double dp[100005];
int seq[100005];
int tp;
int sta[100005];
double X(int x){
return (dp[x]*rate[x])/(a[x]*rate[x]+b[x]);
}
double Y(int x){
return dp[x]/(a[x]*rate[x]+b[x]);
}
double K(int x){
return -a[x]/b[x];
}
bool cmpK(int x,int y){
return a[x]*b[y]>a[y]*b[x];
}
bool cmpK2(int x1,int y1,int x2,int y2){
if(X(x1)==X(y1)){
return 0;
}
if(X(x2)==X(y2)){
return 1;
}
return (Y(y1)-Y(x1))*(X(y2)-X(x2))<(Y(y2)-Y(x2))*(X(y1)-X(x1));
}
bool cmp(int x,int y){
return X(x)<X(y);
}
void cdq(int l,int r){
if(l>=r){
dp[l]=std::max(dp[l],dp[l-1]);
return ;
}
int mid=(l+r)/2;
cdq(l,mid);
for(int i=l;i<=r;i++){
seq[i]=i;
}
std::sort(seq+l,seq+mid+1,cmp);
std::sort(seq+mid+1,seq+r+1,cmpK);
tp=0;
for(int i=l;i<=mid;i++){
while(tp>1&&cmpK2(sta[tp-1],sta[tp],sta[tp],seq[i])){
tp--;
}
sta[++tp]=seq[i];
}
for(int i=mid+1;i<=r;i++){
while(tp>1&&(Y(sta[tp-1])-Y(sta[tp]))/(X(sta[tp-1])-X(sta[tp]))<K(seq[i])){
tp--;
}
int &j=sta[tp];
dp[seq[i]]=std::max(dp[seq[i]],X(j)*a[seq[i]]+Y(j)*b[seq[i]]);
}
cdq(mid+1,r);
return ;
}
int main(){
#ifdef ONLINE_JUDGE
#else
freopen(".in","r",stdin);
freopen(".out","w",stdout);
#endif
int n=read(),s;
s=read();
dp[1]=s;
for(int i=1;i<=n;i++){
scanf("%Lf%Lf%Lf",&a[i],&b[i],&rate[i]);
}
cdq(1,n);
printf("%.3Lf\n",dp[n]);
return 0;
}
inline int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){
c=='-'?f=-1:1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+(c^48);
c=getchar();
}
return f*x;
}
/*
Anything about this program:
Type:
Description:
Example:
1:
In:
Out:
More:
*/