前提条件:
算法步骤:
以P2617 Dynamic Rankings为例。
两种操作:
考虑问题的简化版:只有一次询问,那是不是直接使用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,
整体二分与CDQ分治最大的区别就是一个是基于时间的分治,一个是基于整体的分治;一个是自顶向下的分治思路,一个是自底向上的。
#include
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<
// cout<
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<
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
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;
}