前提条件:
- 题目允许离线算法
- 对于询问,答案具有单调性(例如,区间第k小等)
算法步骤:
- 对于所有的操作离线,保存在结构体中。
- 定义solve(MIN,MAX,st,en)表示对于操作区间 [ s t , e n ] [st,en] [st,en]中的询问,确定的答案值域范围为 [ M I N , M A X ] [MIN,MAX] [MIN,MAX],递归求解。
- 到达边界处 M I N = = M A X MIN==MAX MIN==MAX,则将当前操作序列中所有的查询答案赋值为MIN,回溯。
以P2617 Dynamic Rankings为例。
两种操作:
- 询问区间 [ L , R ] [L,R] [L,R]第 k k k小的数字是谁
- 将 a [ i ] a[i] a[i]改为x
考虑问题的简化版:只有一次询问,那是不是直接使用nth_element函数,O(n)知晓答案。也可以二分答案为 x x x,将序列中小于等于x的数字对应的位置置为1,即可在 n ∗ l o g 2 n n*log_2n n∗log2n时间内知晓答案。
再考虑去掉修改的问题:多次询问,没有修改操作,那是不是可以对每次询问都二分答案,当然如果直接这么做的话复杂度达到了
n
∗
q
∗
l
o
g
2
n
n*q*log_2n
n∗q∗log2n这么高,那就需要用到整体二分了,二分答案,考虑他对于所有询问的贡献。
我们定义了
s
o
l
v
e
(
m
i
n
,
m
a
x
,
s
t
,
e
n
)
solve(min,max,st,en)
solve(min,max,st,en)的意义,那么这里就可以拿来用了,设当前二分的答案为
m
i
d
mid
mid,遇到询问
(
l
,
r
,
k
)
(l,r,k)
(l,r,k),树状数组求得区间中
<
=
m
i
d
<=mid
<=mid的数字个数为
c
n
t
cnt
cnt,
- 如果 c n t > = k cnt>=k cnt>=k,是不是说明了该次询问的答案一定是小于等于 m i d mid mid的,那么对于这种询问我们尝试缩小答案。
- 否则的话,说明答案 > m i d >mid >mid,那么我们将 k k k减去 c n t cnt cnt,然后增大答案。
- 接下来对这两种询问分别处理,那为了处理的方便,我们将这两种询问拆分为两个操作序列,就可以分别处理了。
整体二分与CDQ分治最大的区别就是一个是基于时间的分治,一个是基于整体的分治;一个是自顶向下的分治思路,一个是自底向上的。
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+7;
int a[maxn],b[maxn];
int m;//值域区间;
void quchong(int n){
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-b-1;
}
int getid(int x){
return lower_bound(b+1,b+1+m,x)-b;
}
struct Node{
int l,r,k;
int op;//-1删掉,0加上,>=1查询;
}q[maxn],lq[maxn],rq[maxn];
int ans[maxn];
int sum[maxn];
void add(int x,int y){
for(;x<=m;x+=(x&-x)) sum[x]+=y;
}
int ask(int x){
int res=0;
for(;x;x-=(x&-x)) res+=sum[x];
return res;
}
void solve(int L,int R,int st,int en){
if(L>R) return ;
if(L==R){
for(int i=st;i<=en;++i)
if(q[i].op>0) ans[q[i].op]=b[L];
return ;
}
int mid=(L+R)>>1;
int lt=0,rt=0;
for(int i=st;i<=en;++i)
if(q[i].op==-1){
if(q[i].l<=mid) add(q[i].r,-1),lq[++lt]=q[i];
else rq[++rt]=q[i];
}
else if(q[i].op==0){
if(q[i].l<=mid) add(q[i].r,1),lq[++lt]=q[i];
else rq[++rt]=q[i];
}
else{
int cnt=ask(q[i].r)-ask(q[i].l-1);
if(cnt>=q[i].k) lq[++lt]=q[i];
else q[i].k-=cnt,rq[++rt]=q[i];
}
for(int i=st;i<=en;++i)
if(q[i].l<=mid)
if(q[i].op==-1) add(q[i].r,1);
else if(q[i].op==0) add(q[i].r,-1);
for(int i=1;i<=lt;++i) q[st+i-1]=lq[i];
for(int i=1;i<=rt;++i) q[st+lt+i-1]=rq[i];
solve(L,mid,st,st+lt-1);
solve(mid+1,R,st+lt,en);
}
bool vis[maxn];
char s[9];
int main(){
int n,qq,l,r,k;
scanf("%d%d",&n,&qq);
int t=0;
t=n;
int hh=n;
for(int i=1;i<=n;++i){
scanf("%d",&a[i]);
b[i]=a[i];
q[i].op=0,q[i].l=a[i],q[i].r=i;
}
for(int i=1;i<=qq;++i){
scanf("%s",s);
if(s[0]=='Q'){
++t;
scanf("%d%d%d",&q[t].l,&q[t].r,&q[t].k);
q[t].op=i;
vis[i]=1;
}
else{
scanf("%d%d",&l,&r);
q[++t].op=-1,q[t].l=a[l],q[t].r=l;
q[++t].op=0,q[t].l=r,q[t].r=l;
b[++hh]=r;
a[l]=r;
}
}
quchong(hh);
// for(int i=1;i<=m;++i) cout<<b[i]<<" ";
// cout<<endl;
for(int i=1;i<=t;++i)
if(q[i].op<=0) q[i].l=getid(q[i].l);
// for(int i=1;i<=t;++i)
// cout<<q[i].op<<" "<<q[i].l<<" "<<q[i].r<<" "<<q[i].k<<endl;
solve(1,m,1,t);
for(int i=1;i<=qq;++i)
if(vis[i]) printf("%d\n",ans[i]);
return 0;
}
AcWing 268. 流星
题面见链接。
通过分析题目,有k次修改,n次询问,而且修改都发生在询问之前,同时对于每一次查询,答案都具有二分的性质,即陨石雨越多能收集到的石头越多,那么就可以整体二分,每次二分操作次数,将询问序列分为两半。
注意加法会炸long long。
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+7;
typedef long long ll;
struct Node{
int l,r,x;
}a[maxn];
struct AAA{
int need;
int id;
}q[maxn],lq[maxn],rq[maxn];
ll sum[maxn];
int m;
void add(int x,int y){
for(;x<=m;x+=x&-x) sum[x]+=y;
}
ll ask(int x){
ll res=0;
for(;x;x-=x&-x) res+=sum[x];
return res;
}
vector<int> v[maxn];
int ans[maxn];
void solve(int L,int R,int st,int en){
if(L>R) return ;
//if(st>en) return ;
if(L==R){
for(int i=st;i<=en;++i) ans[q[i].id]=L;
return ;
}
int mid=(L+R)>>1;
int lt=0,rt=0;
for(int i=L;i<=mid;++i){
if(a[i].l>a[i].r) add(1,a[i].x);
add(a[i].l,a[i].x);
add(a[i].r+1,-a[i].x);
}
for(int i=st;i<=en;++i){
long double cnt=0;
for(int j=0;j<v[q[i].id].size();++j){
cnt+=ask(v[q[i].id][j]);
}
if(cnt>=q[i].need) lq[++lt]=q[i];
else q[i].need-=(ll)cnt,rq[++rt]=q[i];
}
for(int i=L;i<=mid;++i){
if(a[i].l>a[i].r) add(1,-a[i].x);
add(a[i].l,-a[i].x);
add(a[i].r+1,a[i].x);
}
for(int i=1;i<=lt;++i) q[st+i-1]=lq[i];
for(int i=1;i<=rt;++i) q[st+lt+i-1]=rq[i];
solve(L,mid,st,st+lt-1);
solve(mid+1,R,st+lt,en);
}
const int inf=0x3f3f3f3f;
int main(){
int n,x;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i){
scanf("%d",&x);
v[x].push_back(i);
}
for(int i=1;i<=n;++i){
scanf("%d",&q[i].need);
q[i].id=i;
}
int k;
scanf("%d",&k);
for(int i=1;i<=k;++i)
scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].x);
++k;
a[k].l=1,a[k].r=m,a[k].x=inf;
solve(1,k,1,n);
for(int i=1;i<=n;++i)
if(ans[i]==k) printf("NIE\n");
else printf("%d\n",ans[i]);
return 0;
}