CDQ分治与整体二分小结
分类:
IT文章
•
2022-07-18 10:35:46
面对复杂的修改查询高维问题,往往需要高级数据结构解决,但是高级数据结构一般码量大,容易犯错。对于一些离线问题,我们可以用CDQ分治或者整体二分通过降维等方法解决,且因为CDQ分治容易理解且十分好写受到许多算法竞赛选手的欢迎。
CDQ分治
推荐博客:http://www.cnblogs.com/mlystdcall/p/6219421.html
CDQ分治的思想就是,左右分治然后只计算左边修改操作对右边查询操作的影响。CDQ分治只是提供了一种分治思想,没有什么固定的模板,需要具体问题具体分析,要多多做题才能体会。
园丁的烦恼 SHOI2007 BZOJ 1935
模板题,(时间,x坐标,y坐标)三维偏序问题,因为时间按照输入已经天然排好序所以不用排序。
#include<bits/stdc++.h>
using namespace std;
const int N=3e6+10;
typedef long long LL;
int n,m,qid;
LL ans[N];
struct query{
int type,x,y,aid,w; //操作类型,x坐标,y坐标,答案编号,对答案贡献
bool operator < (const query &rhs) const {
return x==rhs.x ? type<rhs.type : x<rhs.x; //x相等时要修改操作在前
}
}Q[N],tmp[N];
const int L=1e7+10; LL sum[L];
void update(int x,int v) {
if (x) for (;x<L;x+=x&-x) sum[x]+=v;
}
LL query2(int x) {
LL ret=0;
if (x) for (;x;x-=x&-x) ret+=sum[x];
return ret;
}
void Clear(int x) {
if (x) for (;x<L && sum[x];x+=x&-x) sum[x]=0;
}
void CDQ(int l,int r) {
if (l>=r) return;
int mid=l+r>>1;
CDQ(l,mid); CDQ(mid+1,r); //先两边分治
int p=l,q=mid+1,o=l-1; //归并排序合并两边并计算左边操作对右边询问影响
while (p<=mid && q<=r) {
if (Q[p]<Q[q]) {
if (Q[p].type==0) update(Q[p].y,1); //只处理左边的修改操作
tmp[++o]=Q[p++]; //归并排序
} else {
if (Q[q].type==1) ans[Q[q].aid]+=query2(Q[q].y)*Q[q].w; //只处理右边的查询操作
tmp[++o]=Q[q++]; //归并排序
}
}
while (p<=mid) tmp[++o]=Q[p++];
while (q<=r) {
if (Q[q].type==1) ans[Q[q].aid]+=query2(Q[q].y)*Q[q].w;
tmp[++o]=Q[q++];
}
for(int i=l;i<=mid;i++) if(Q[i].type==0) Clear(Q[i].y); //清空树状数组
for (int i=l;i<=r;i++) Q[i]=tmp[i]; //记录归并排序后结果
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++) {
int x,y; scanf("%d%d",&x,&y); x++; y++;
Q[++qid]=(query){0,x,y,0,0};
}
for (int i=1;i<=m;i++) {
int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
x1++; y1++; x2++; y2++;
Q[++qid]=(query){1,x2,y2,i,1};
Q[++qid]=(query){1,x1-1,y1-1,i,1};
Q[++qid]=(query){1,x1-1,y2,i,-1};
Q[++qid]=(query){1,x2,y1-1,i,-1};
}
CDQ(1,qid);
for (int i=1;i<=m;i++) printf("%lld
",ans[i]);
return 0;
}
View Code
Mokia BZOJ 1176 / 简单题BZOJ 2683
这两题似乎是一样的,在上一题基础上修改即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e6+10;
int n,s,qid,ansid;
LL ans[N];
struct query{
int type,x,y,v,w,aid;
bool operator < (const query &rhs) const {
return x==rhs.x ? type<rhs.type : x<rhs.x;
}
}Q[N],tmp[N];
const int L=2e6; LL sum[L];
void update(int x,LL v) {
if (x) for (;x<=n;x+=x&-x) sum[x]+=v;
}
LL query2(int x) {
LL ret=0;
if (x) for (;x;x-=x&-x) ret+=sum[x];
return ret;
}
void Clear(int x) {
if (x) for (;x<=n && sum[x];x+=x&-x) sum[x]=0;
}
void CDQ(int l,int r) {
if (l>=r) return;
int mid=l+r>>1;
CDQ(l,mid); CDQ(mid+1,r);
int p=l,q=mid+1,o=l-1;
while (p<=mid && q<=r) {
if (Q[p]<Q[q]) {
if (Q[p].type==0) update(Q[p].y,Q[p].v);
tmp[++o]=Q[p++];
} else {
if (Q[q].type==1) ans[Q[q].aid]+=Q[q].w*query2(Q[q].y);
tmp[++o]=Q[q++];
}
}
while (p<=mid) tmp[++o]=Q[p++];
while (q<=r) {
if (Q[q].type==1) ans[Q[q].aid]+=Q[q].w*query2(Q[q].y);
tmp[++o]=Q[q++];
}
for (int i=l;i<=mid;i++) if (Q[i].type==0) Clear(Q[i].y);
for (int i=l;i<=r;i++) Q[i]=tmp[i];
}
int main()
{
cin>>n; s=0;
qid=0; ansid=0; int opt;
while (scanf("%d",&opt)==1 && opt!=3) {
int x1,y1,x2,y2,v;
if (opt==2) {
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
ans[++ansid]=(LL)s*(x2-x1+1)*(y2-x1+1);
Q[++qid]=(query){1,x2,y2,0,1,ansid};
Q[++qid]=(query){1,x1-1,y2,0,-1,ansid};
Q[++qid]=(query){1,x2,y1-1,0,-1,ansid};
Q[++qid]=(query){1,x1-1,y1-1,0,1,ansid};
} else {
scanf("%d%d%d",&x1,&y1,&v);
Q[++qid]=(query){0,x1,y1,v,0,0};
}
}
CDQ(1,qid);
for (int i=1;i<=ansid;i++) printf("%lld
",ans[i]);
return 0;
}
View Code
陌上花开 BZOJ 3262
比较裸的三维偏序问题,CDQ最擅长解决了。
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=100000+10;
const int M=200000+10;
struct rec{
int x,y,z,id;
int maxid;
bool operator < (const rec &rhs) const {
return x<rhs.x || x==rhs.x && y<rhs.y || x==rhs.x && y==rhs.y && z<rhs.z;
}
}f[N],t[N];
int n,m;
int c[M<<1],ans[N];
void update(int x,int v) {
for (;x<=m;x+=x&-x) c[x]+=v;
}
int query(int x) {
int res=0;
for (;x;x-=x&-x) res+=c[x];
return res;
}
void slove(int l,int r) {
if (l==r) return;
int mid=(l+r)>>1;
slove(l,mid);
slove(mid+1,r);
int lc=l,rc=mid+1;
for (int i=l;i<=r;i++) {
if (lc<=mid && f[lc].y<=f[rc].y || rc>r) t[i]=f[lc++];
else t[i]=f[rc++];
}
for (int i=l;i<=r;i++) {
if (t[i].id<=mid) update(t[i].z,1);
else ans[t[i].id]+=query(t[i].z);
}
for (int i=l;i<=r;i++) {
f[i]=t[i];
if (f[i].id<=mid) update(f[i].z,-1);
}
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
scanf("%d%d%d",&f[i].x,&f[i].y,&f[i].z);
sort(f+1,f+n+1);
for (int i=1;i<=n;i++) f[i].id=i;
int to=0;
for (int i=1;i<=n;i++) {
if (i<=to) continue;
while (f[i].x==f[to+1].x && f[i].y==f[to+1].y && f[i].z==f[to+1].z) to++;
for (int j=i;j<=to;j++) f[j].maxid=to;
}
slove(1,n);
for (int i=1;i<=n;i++) ans[f[i].id]=ans[f[i].maxid];
sort(ans,ans+n+1);
int now=1;
for (int i=0;i<n;i++) {
int sum=0;
while (now<=n && ans[now]==i) { now++; sum++; }
printf("%d
",sum);
}
return 0;
}
View Code
动态逆序对 CQOI2011 BZOJ 3295
这道题很有意思也有多种解法,非常值得多多思考!
这里讲的是CDQ分治的解法(其实哪怕是CDQ分治也有两种解法):看到这道题我们会比较自然想到把删除变成插入,那么我们增加一个时间纬度,每一个操作变成(插入时间,位置,值的大小)的三维数对,这时候每一个插入也是每一个查询,我们以CDQ的方式去思考对于一个插入数对:别的插入数对对它的影响是什么?对于数对(tim2,pos2,val2),它对总逆序对增加的贡献就是 tim1<tim2 && pos1<pos2 && val1>val2 的个数加上 tim1<tim2 && pos1>pos2 && val1<val2 的个数。发现没有这是一个经典的三维偏序问题!CDQ分治可解!
这道题还是有一些细节的,都在代码注释里了。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
int n,m,qid,a[N],b[N],c[N],pos[N];
LL ans[N];
struct query{
int tim,pos,val;
bool operator < (const query &rhs) const {
return pos<rhs.pos;
}
}Q[N],tmp[N];
LL sum[N];
void update(int x,int v) {
for (;x<=n;x+=x&-x) sum[x]+=v;
}
LL query2(int x) {
LL ret=0;
for (;x;x-=x&-x) ret+=sum[x];
return ret;
}
void CDQ(int l,int r) {
if (l>=r) return;
int mid=l+r>>1;
CDQ(l,mid); CDQ(mid+1,r);
int p=l,q=mid+1,o=l-1;
while (p<=mid || q<=r) //计算tim1<tim2 && pos1<pos2 && val1>val2 的逆序对
if (q>r || p<=mid && Q[p]<Q[q]) update(Q[p].val,1),tmp[++o]=Q[p++];
else ans[Q[q].tim]+=query2(n)-query2(Q[q].val),tmp[++o]=Q[q++];
for (int i=l;i<=mid;i++) update(Q[i].val,-1);
p=mid; q=r; //这里是一个小细节,因为左右区间是从小到大排序,而这里需要从大到小,所以倒序循环
while (p>=l || q>mid) //计算tim1<tim2 && pos1>pos2 && val1<val2 的逆序对
if (q<=mid || p>=l && Q[q]<Q[p]) update(Q[p].val,1),p--;
else ans[Q[q].tim]+=query2(Q[q].val-1),q--;
for (int i=l;i<=mid;i++) update(Q[i].val,-1);
for (int i=l;i<=r;i++) Q[i]=tmp[i]; //归并结果
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++) scanf("%d",&a[i]),pos[a[i]]=i;
for (int i=1;i<=m;i++) scanf("%d",&b[i]),c[b[i]]=1;
for (int i=1;i<=n;i++)
if (!c[i]) qid++,Q[qid]=(query){qid,pos[i],i};
for (int i=m;i;i--) qid++,Q[qid]=(query){qid,pos[b[i]],b[i]};
CDQ(1,qid);
for (int i=1;i<=n;i++) ans[i]+=ans[i-1];
for (int i=n;i>n-m;i--) printf("%lld
",ans[i]);
return 0;
}
View Code
【BZOJ2001】[HNOI2010]城市建设 / 洛谷P3206
【BZOJ2141】排队
这道题和动态逆序对比较像,只是删除操作变成了交换。解法:交换操作可以变成删除加插入操作,那么这题就变成了 (时间,位置,值)的三维偏序问题,考虑用CDQ分治解决:时间排序,位置分治,值树状数组。
但是这里要特别注意的是:本题因为有多个插入删除操作,所以多能会有多个操作的位置是相同的,但是逆序对要求的是位置比它小/大,值比它大/小的对,就是位置相等的不能算贡献。所以我们对CDQ分治的第二维的归并顺序就有讲究:必须是位置严格小于才放到左边然后插入树状数组,也就是说左边位置必须严格小于右边位置才算贡献。
其实这样说也不太对,应该说CDQ分治的本质是:对于一个询问,它的答案就是所有比它早的修改对这个询问产生的影响累加的结果。也就是说其实分治的顺序并不是十分重要,只要达到了上面这个目标即可,但是往往我们为了方便统计答案会修改分治顺序使得这样做利于统计答案。举个例子就是像这题哪怕把分治顺序改为return x<rhs.x || x==rhs.x && z<rhs.z;只要把统计答案改为Q[p].x<Q[q].x其实也是OK的,只不过会使得分治和统计答案分开了而已。(蒟蒻口胡)
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m,qid,a[N],b[N],ans[N];
struct rec{
int x,y,z,aid; //位置,插入/删除,值,答案下标
bool operator < (const rec &rhs) const {
return x<rhs.x;
}
}Q[N],tmp[N];
int sum[N];
void update(int x,int v) {
for (;x<=n;x+=x&-x) sum[x]+=v;
}
int query(int x) {
int ret=0;
for (;x;x-=x&-x) ret+=sum[x];
return ret;
}
void CDQ(int l,int r) {
if (l>=r) return;
int mid=l+r>>1;
CDQ(l,mid); CDQ(mid+1,r);
int p=l,q=mid+1,o=l-1;
while (p<=mid || q<=r) //归并排序结果
if (q>r || p<=mid && Q[p]<Q[q]) tmp[++o]=Q[p++]; else tmp[++o]=Q[q++];
p=l; q=mid+1;
while (p<=mid || q<=r) //正向算逆序对
if (q>r || p<=mid && Q[p]<Q[q]) update(Q[p].z,Q[p].y),p++;
else ans[Q[q].aid]+=Q[q].y*(query(n)-query(Q[q].z)),q++;
for (int i=l;i<=mid;i++) update(Q[i].z,-Q[i].y);
p=mid; q=r;
while (p>=l || q>mid) //反向算逆序对
if (q<=mid || p>=l && Q[q]<Q[p]) update(Q[p].z,Q[p].y),p--;
else ans[Q[q].aid]+=Q[q].y*(query(Q[q].z-1)),q--;
for (int i=l;i<=mid;i++) update(Q[i].z,-Q[i].y);
for (int i=l;i<=r;i++) Q[i]=tmp[i];
}
int main()
{
cin>>n;
for (int i=1;i<=n;i++) scanf("%d",&a[i]),b[i]=a[i];
sort(b+1,b+n+1);
int t=unique(b+1,b+n+1)-(b+1);
for (int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+t+1,a[i])-b;
for (int i=1;i<=n;i++) Q[++qid]=(rec){i,1,a[i],0};
cin>>m;
for (int i=1;i<=m;i++) {
int x,y; scanf("%d%d",&x,&y);
Q[++qid]=(rec){x,-1,a[x],i};
Q[++qid]=(rec){x,1,a[y],i};
Q[++qid]=(rec){y,-1,a[y],i};
Q[++qid]=(rec){y,1,a[x],i};
swap(a[x],a[y]);
}
CDQ(1,qid);
for (int i=0;i<=m;i++) {
if (i) ans[i]+=ans[i-1];
printf("%d
",ans[i]);
}
return 0;
}
View Code
【BZOJ3745】Norma
整体二分
https://blog.****.net/hbhcy98/article/details/50642773
https://www.cnblogs.com/Sakits/p/7990875.html
上面博客的大佬说得很好了。整体二分就是二分答案的进阶,就是把操作和答案区间一起二分,通过算左边区间对答案贡献继续向左右分,最终缩小答案区间得到答案。这里需要特别强调的是把操作和答案一起二分,也就是说当前答案区间[L,R]二分[L,M]和[M+1,R]时,应该先把操作(包括查询也修改)分到它属于的答案区间,在继续往下分。
整体二分和CDQ分治有点儿相似都是离线分治算法,前者基于值域后者基于时间,时间复杂度都是logn*f(n) 这里f(n)是分治的一层的计算时间。
POJ-2104 K-th Number
经典题,可以用多种解法,也可以当作整体二分模板题来练。
#include<iostream>
#include<cstdio>
using namespace std;
const int N=2e5+10;
const int INF=0x3f3f3f3f;
int n,m,qid,a[N],ans[N];
struct rec{
int type,x,y,z,aid;
}Q[N],ql[N],qr[N];
int sum[N];
void update(int x,int v) {
for (;x<=n;x+=x&-x) sum[x]+=v;
}
int query(int x) {
int ret=0;
for (;x;x-=x&-x) ret+=sum[x];
return ret;
}
void solve(int L,int R,int l,int r) { //答案值域[L,R],操作区间[l,r]
if (l>r) return; //操作序列为空
if (L==R) {
for (int i=l;i<=r;i++)
if (Q[i].type==1) ans[Q[i].aid]=L; //答案唯一了
return;
}
int M=L+R>>1,p=0,q=0;
for (int i=l;i<=r;i++) {
if (Q[i].type==0) { //修改操作
if (Q[i].z<=M) ql[++p]=Q[i],update(Q[i].x,1); //注意这里树状数组插入的是位置
else qr[++q]=Q[i];
} else { //查询操作
int tmp=query(Q[i].y)-query(Q[i].x-1); //查询位置区间[x,y]中值域在[L,M]的数的个数
if (tmp>=Q[i].z) ql[++p]=Q[i];
else Q[i].z-=tmp,qr[++q]=Q[i];
}
}
for (int i=l;i<=r;i++)
if (Q[i].type==0 && Q[i].z<=M) update(Q[i].x,-1); //还原树状数组
for (int i=1;i<=p;i++) Q[l+i-1]=ql[i]; //把操作按答案值域分开
for (int i=1;i<=q;i++) Q[l+p+i-1]=qr[i];
solve(L,M,l,l+p-1); solve(M+1,R,l+p,r); //继续分治
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
Q[++qid]=(rec){0,i,0,a[i],0};
for (int i=1;i<=m;i++) {
int l,r,k; scanf("%d%d%d",&l,&r,&k);
Q[++qid]=(rec){1,l,r,k,i};
}
solve(-INF,INF,1,qid);
for (int i=1;i<=m;i++) printf("%d
",ans[i]);
return 0;
}
View Code
BZOJ-1901: Zju2112 Dynamic Rankings
这道题是上一道题的动态版,我的博客写了树套树的做法,但是树套树不仅代码复杂容易写错而且常数大,这里用试着用整体二分解决。
这题比起上一题就是多了修改操作,那么我们把a[x]=y这个赋值操作拆开变成 ①删除x位置的a[x] ②插入x位置的y 。原来的初始数组就可以变成这里的操作② 。把操作拆分后同样整体二分树状数组做法就可以AC了。有一个问题蒟蒻想了很久:为什么这样是对的,插入删除查询混合在一起不会错吗?后来想通了,因为我们按照输入顺序安排操作顺序的,所以操作顺序是按照时间顺序的,且因为这里是按答案值域,所以一个赋值操作先前的a[x]的插入和删除必定会分在同一个区间,那么经过紧贴在一起的+1和-1之后,相当于a[x]完全没有发挥过作用所以一个a[x]=y的赋值操作先前的a[x]是完全不会对答案影响的!这个思路十分巧妙运用了基于值域二分的特性。
#include<iostream>
#include<cstdio>
using namespace std;
const int N=3e5+10;
const int INF=0x3f3f3f3f;
int n,m,qid,ansid,a[N],ans[N];
struct rec{
int type,x,y,z,aid;
}Q[N],ql[N],qr[N];
int sum[N];
void update(int x,int v) {
for (;x<=n;x+=x&-x) sum[x]+=v;
}
int query(int x) {
int ret=0;
for (;x;x-=x&-x) ret+=sum[x];
return ret;
}
void solve(int L,int R,int l,int r) { //答案值域[L,R],操作区间[l,r]
if (L>R || l>r) return; //操作序列为空
if (L==R) {
for (int i=l;i<=r;i++)
if (Q[i].type==1) ans[Q[i].aid]=L; //答案唯一了
return;
}
int M=L+R>>1,p=0,q=0;
for (int i=l;i<=r;i++) {
if (Q[i].type==0) { //修改操作
if (Q[i].z<=M) ql[++p]=Q[i],update(Q[i].x,Q[i].y); //注意这里树状数组插入的是位置
else qr[++q]=Q[i];
} else { //查询操作
int tmp=query(Q[i].y)-query(Q[i].x-1); //查询位置区间[x,y]中值域在[L,M]的数的个数
if (tmp>=Q[i].z) ql[++p]=Q[i];
else Q[i].z-=tmp,qr[++q]=Q[i];
}
}
for (int i=l;i<=r;i++)
if (Q[i].type==0 && Q[i].z<=M) update(Q[i].x,-Q[i].y); //还原树状数组
for (int i=1;i<=p;i++) Q[l+i-1]=ql[i]; //把操作按答案值域分开
for (int i=1;i<=q;i++) Q[l+p+i-1]=qr[i];
solve(L,M,l,l+p-1); solve(M+1,R,l+p,r); //继续分治
}
int main()
{
cin>>n>>m;
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
Q[++qid]=(rec){0,i,1,a[i],0};
for (int i=1;i<=m;i++) {
char c = '_';while(c != 'C' && c != 'Q') c = getchar();
int x,y,k;
if (c=='Q') {
scanf("%d%d%d",&x,&y,&k);
Q[++qid]=(rec){1,x,y,k,++ansid};
} else {
scanf("%d%d",&x,&y);
Q[++qid]=(rec){0,x,-1,a[x],0};
Q[++qid]=(rec){0,x,1,y,0};
a[x]=y;
}
}
solve(-INF,INF,1,qid);
for (int i=1;i<=ansid;i++) printf("%d
",ans[i]);
return 0;
}
View Code
BZOJ-3110: [Zjoi2013]K大数查询
这道题题意有点太简略了点,注意题目 “第a个位置到第b个位置,每个位置加入一个数c” 的意思不是令ab区间的数+=c,而是每个位置能够容纳多个数,令[a,b]区间每个位置都插入多一个数c。这道题可以用树套树做,也可以用常数更小的整体二分做。
这里说整体二分的做法:比起静态的区间第K小问题,这里多了个位置插入操作,并且是区间的位置插入,按照我们上两题整体二分的做法,还是二分答案值域+数据结构把操作分开,但是这里需要用到区间修改和区间查询,树状数组当然也能做到所以我选择了线段树。
这题还有一些小细节:①答案要求的是区间第K大,其实也可以直接修改整体二分的代码直接变成第K大,有一个懒点的做法是注意到操作一的c绝对值小于等于n,所以我们可以把c变成n-c+1做第K小整体二分,输出答案的时候再还原回去即可。②注意到n<=50000 那么 n*m=2.5*10^9 > int=2.1*10^9 所以要用longlong
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
typedef long long LL;
int n,m,qid,ansid;
LL ans[N];
struct rec{
LL type,x,y,z,aid;
}Q[N],q1[N],q2[N];
LL sum[N<<2],tag[N<<2];
void pushup(int rt) {
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void pushdown(int rt,LL len1,LL len2) {
sum[rt<<1]+=tag[rt]*len1; tag[rt<<1]+=tag[rt];
sum[rt<<1|1]+=tag[rt]*len2; tag[rt<<1|1]+=tag[rt];
tag[rt]=0;
}
void update(int rt,int l,int r,int ql,int qr,LL v) {
if (ql<=l && r<=qr) {
sum[rt]+=v*(r-l+1); tag[rt]+=v;
return;
}
int mid=l+r>>1;
pushdown(rt,mid-l+1,r-mid);
if (ql<=mid) update(rt<<1,l,mid,ql,qr,v);
if (qr>mid) update(rt<<1|1,mid+1,r,ql,qr,v);
pushup(rt);
}
LL query(int rt,int l,int r,int ql,int qr) {
if (ql<=l && r<=qr) return sum[rt];
int mid=l+r>>1; LL ret=0;
pushdown(rt,mid-l+1,r-mid);
if (ql<=mid) ret+=query(rt<<1,l,mid,ql,qr);
if (qr>mid) ret+=query(rt<<1|1,mid+1,r,ql,qr);
return ret;
}
void solve(LL L,LL R,int l,int r) {
if (l>r) return;
if (L==R) {
for (int i=l;i<=r;i++)
if (Q[i].type==1) ans[Q[i].aid]=L;
return;
}
LL M=(L+R)/2,p=0,q=0;
for (int i=l;i<=r;i++)
if (Q[i].type==0) {
if (Q[i].z<=M) q1[++p]=Q[i],update(1,1,n,Q[i].x,Q[i].y,1);
else q2[++q]=Q[i];
} else {
LL tmp=query(1,1,n,Q[i].x,Q[i].y);
if (tmp>=Q[i].z) q1[++p]=Q[i];
else Q[i].z-=tmp,q2[++q]=Q[i];
}
for (int i=l;i<=r;i++)
if (Q[i].type==0 && Q[i].z<=M) update(1,1,n,Q[i].x,Q[i].y,-1);
for (int i=1;i<=p;i++) Q[l+i-1]=q1[i];
for (int i=1;i<=q;i++) Q[l+p+i-1]=q2[i];
solve(L,M,l,l+p-1); solve(M+1,R,l+p,r);
}
int main()
{
cin>>n>>m;
for (int i=1;i<=m;i++) {
LL opt,x,y,z; scanf("%lld%lld%lld%lld",&opt,&x,&y,&z);
if (opt==1) Q[++qid]=(rec){0,x,y,n-z+1,0};
else Q[++qid]=(rec){1,x,y,z,++ansid};
}
solve(-n,n,1,qid);
for (int i=1;i<=ansid;i++) printf("%lld
",n-ans[i]+1);
return 0;
}
View Code
BZOJ-2738: 矩阵乘法
二维的区间第K小,把一维树状数组改成二维的即可。这题似乎卡常,二分边界不要用INF,对数据取个max代替INF。1.9sAC你敢信hhh。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int INF=0x3f3f3f3f;
int n,m,qid,ans[N];
struct rec{
int type,x1,y1,x2,y2,z,aid;
}Q[N],q1[N],q2[N];
int sum[510][510];
void update(int x,int y,int v) {
for (int i=x;i<=n;i+=i&-i)
for (int j=y;j<=n;j+=j&-j)
sum[i][j]+=v;
}
int query(int x,int y) {
int ret=0;
for (int i=x;i;i-=i&-i)
for (int j=y;j;j-=j&-j)
ret+=sum[i][j];
return ret;
}
void solve(int L,int R,int l,int r) {
if (l>r) return;
if (L==R) {
for (int i=l;i<=r;i++)
if (Q[i].type==1) ans[Q[i].aid]=L;
return;
}
int M=L+R>>1,p=0,q=0;
for (int i=l;i<=r;i++) {
if (Q[i].type==0) {
if (Q[i].z<=M) update(Q[i].x1,Q[i].y1,1),q1[++p]=Q[i];
else q2[++q]=Q[i];
} else {
int tmp=query(Q[i].x2,Q[i].y2)+query(Q[i].x1-1,Q[i].y1-1)-query(Q[i].x1-1,Q[i].y2)-query(Q[i].x2,Q[i].y1-1);
if (tmp>=Q[i].z) q1[++p]=Q[i];
else Q[i].z-=tmp,q2[++q]=Q[i];
}
}
for (int i=l;i<=r;i++)
if (Q[i].type==0 && Q[i].z<=M) update(Q[i].x1,Q[i].y1,-1);
for (int i=1;i<=p;i++) Q[l+i-1]=q1[i];
for (int i=1;i<=q;i++) Q[l+p+i-1]=q2[i];
solve(L,M,l,l+p-1); solve(M+1,R,l+p,r);
}
int main()
{
cin>>n>>m;
int Max=0;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) {
int x; scanf("%d",&x);
Q[++qid]=(rec){0,i,j,0,0,x,0};
Max=max(Max,x);
}
for (int i=1;i<=m;i++) {
int x1,y1,x2,y2,z; scanf("%d%d%d%d%d",&x1,&y1,&x2,&y2,&z);
Q[++qid]=(rec){1,x1,y1,x2,y2,z,i};
}
solve(0,Max,1,qid);
for (int i=1;i<=m;i++) printf("%d
",ans[i]);
return 0;
}
View Code