整体二分
简述
相当类似 CDQ 的一种算法。说的冠冕一点,是对于每个 mid 批量处理查询的算法。
更加简单的说法就是:二分答案,对于每个 \(mid\),用二维数点之类的方法判断每个查询的答案是大于 \(mid\) 还是小于 \(mid\),据此将查询分为两类继续递归下去。(一般来说会把修改也放进来,使用类似 cdq 的方法离线维护)
一般适用于离线可带修区间第 \(k\) 大、第 \(k\) 小的问题,记得转化为这种问题时想到整体二分。
标程
目标题目:P2617 Dynamic Rankings(而不是模板题)
要说的都在代码里了。
/*
主体来说,就是把查询和修改按顺序放到一个数组里。
二分的时候,按顺序,<=mid的修改改动其下标+1,查询时根据下标范围内是否>k来判断在左侧还是右侧(二维数点)
最后把数组合并,并清空树状数组
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=1e5+10;
ll n,m;
ll a[N],c[N],t[N],ans[N];
inline ll lowbit(ll x)
{
return x&-x;
}
ll ask(ll p)
{
ll res=0;
for(ll i=p;i;i-=lowbit(i))
res+=t[i];
return res;
}
void add(ll p,ll x)
{
if(!p) return;
for(ll i=p;i<=n;i+=lowbit(i))
t[i]+=x;
}
//type:0 表示询问,非0表示修改
//id:表示修改的位置(感觉放到k里更好)
//l,r,k:表示查询[l,r]的第k小
//v:表示修改为的值
struct node
{
ll type,id;
ll l,r,k;
ll v;
}q[N*3],q1[N*3],q2[N*3];//q存放所有修改和查询(按顺序),q1存放将要递归至左边的,q2存放右边的
ll cnt;
void solve(ll lq,ll rq,ll lv,ll rv)
{
if(lq>rq)
return;
if(lv==rv)
{
for(ll i=lq;i<=rq;i++)
if(!q[i].type)
ans[q[i].id]=lv;
return;
}
ll mid=(lv+rv)>>1,cnt1=0,cnt2=0;
for(ll i=lq;i<=rq;i++)
{
if(q[i].type)
{
if(q[i].v<=mid)
{
add(q[i].id,q[i].type);//修改
q1[++cnt1]=q[i];
}
else
q2[++cnt2]=q[i];
}
else
{
ll num=ask(q[i].r)-ask(q[i].l-1);//查询下标在[l,r]间的<=mid数有多少
if(num>=q[i].k)//注意符号
q1[++cnt1]=q[i];
else
{
q[i].k-=num;
q2[++cnt2]=q[i];
}
}
}
for(ll i=lq;i<=rq;i++)//清空BIT
if(q[i].type && q[i].v<=mid)
add(q[i].id,-q[i].type);
for(ll i=1;i<=cnt1;i++)//合并两个数组
q[lq+i-1]=q1[i];
for(ll i=1;i<=cnt2;i++)
q[lq+cnt1+i-1]=q2[i];
solve(lq,lq+cnt1-1,lv,mid);
solve(lq+cnt1,rq,mid+1,rv);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n>>m;
for(ll i=1;i<=n;i++)
{
cin>>a[i];
q[++cnt]={1,i,0,0,0,a[i]};
}
ll cntq=0;
for(ll i=1;i<=m;i++)
{
char op;
cin>>op;
if(op=='Q')
{
cnt++;
cin>>q[cnt].l>>q[cnt].r>>q[cnt].k;
q[cnt].type=0;
q[cnt].id=++cntq;
}
else
{
ll p,x;
cin>>p>>x;
q[++cnt]={-1,p,0,0,0,a[p]};
a[p]=x;
q[++cnt]={1,p,0,0,0,a[p]};
}
}
solve(1,cnt,0,1e9);
for(ll i=1;i<=cntq;i++)
cout<<ans[i]<<"\n";
return 0;
}
调题记录
全都是基于模板改一下计数方法或者转化一下题面。
P1527 [国家集训队] 矩阵乘法
单修矩查树状数组即可。\(\color{red}\text{WA}\):空间开小了。
P7424 [THUPC2017] 天天爱射击
转化题意:每块木板所在区间内,射出时间第 \(k\) 早的子弹是哪一个。
\(\color{red}\text{WA}\):有的木板一直不会碎,二分的初始值域设小了。也要小心值域设太大越界。
P4175 [CTSC2008] 网络管理
一个很 naive 的想法是树链剖分维护链上内容,但是完全没必要。只需树状数组维护返根链上的计数,查询就是树上差分,修改就是子树修改(摊成dfn序即可)。
\(\color{red}\text{WA}\):求的是第 \(k\) 大,不是第 \(k\) 小!由于一个点有两个编号(id、dfn),把他们两个搞混了,还没法用样例测出来。此时可以考虑分块瞪眼法,先确定一部分的正确性,再去瞪下一部分。