整体二分
例题
MKTHNUM - K-th Number
考虑如果我们对每个操作进行二分怎么做。
显然是对这个区间不大于二分值
然后我们考虑推广这种思路。
首先我们询问的答案肯定是原来数组中的一个数,所以我们可以直接把原数组排序,然后用下标代表其权值。这里我们的排序事实上可以用离散化完成,那样做的效果相同,但是排序更方便。
接下来,我们要实现整体二分的核心函数
当然,给出的询问不会这么巧合正好是上面那种情况的,所以我们在整体二分时需要对询问排序,当然这个后面会说。
因为答案在
然后考虑如果对于一个询问
这里说一下为什么要
然后做完这个类似于归并排序的操作后,我们把之前树状数组里的东西清空掉,然后把
边界情况:
代码:
#include<bits/stdc++.h>
#define int long long
#define N 100005
using namespace std;
int n,m,c[N],res[N];
struct node{
int id,x;
bool operator<(const node &t)const{
return x<t.x;
}
}a[N];
struct ask{
int id,l,r,k;
}q[N],q1[N],q2[N];
int lowbit(int x){
return x&-x;
}
void modify(int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
int qry(int x){
int res=0;
while(x){
res+=c[x];
x-=lowbit(x);
}
return res;
}
void solve(int l,int r,int L,int R){
if(l>r)return;
if(L==R){
for(int i=l;i<=r;i++){
res[q[i].id]=a[L].x;
}
return;
}
int mid=L+R>>1;
for(int i=L;i<=mid;i++){
modify(a[i].id,1);
}
int cnt1=0,cnt2=0;
for(int i=l;i<=r;i++){
int sum=qry(q[i].r)-qry(q[i].l-1);
if(q[i].k<=sum){
q1[++cnt1]=q[i];
}
else{
q[i].k-=sum;
q2[++cnt2]=q[i];
}
}
for(int i=L;i<=mid;i++){
modify(a[i].id,-1);
}
for(int i=1;i<=cnt1;i++){
q[i+l-1]=q1[i];
}
for(int i=1;i<=cnt2;i++){
q[i+l+cnt1-1]=q2[i];
}
solve(l,l+cnt1-1,L,mid);
solve(l+cnt1,r,mid+1,R);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i].x;
a[i].id=i;
}
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r>>q[i].k;
q[i].id=i;
}
sort(a+1,a+n+1);
solve(1,m,1,n);
for(int i=1;i<=m;i++){
cout<<res[i]<<'\n';
}
return 0;
}
K大数查询
上一道题的动态版。这里说一句,如果没有第 -1
。
注意这道题需要支持区间加区间查询,所以我们需要线段树或者两个树状数组进行维护。这里笔者采用了两个树状数组。
就比方说要求
然后剩下的东西就跟上一道题一样。代码:
#include<bits/stdc++.h>
#define int long long
#define N 50005
using namespace std;
int n,m,res[N],c1[N],c2[N];
struct node{
int op,l,r,c,id;
}q[N],q1[N],q2[N];
int lowbit(int x){
return x&-x;
}
void bit_modify(int c[],int x,int v){
while(x<=n){
c[x]+=v;
x+=lowbit(x);
}
}
int bit_qry(int c[],int x){
int res=0;
while(x){
res+=c[x];
x-=lowbit(x);
}
return res;
}
void modify(int x,int v){
bit_modify(c1,x,v);
bit_modify(c2,x,v*(x-1));//这里是树状数组区间加区间查询模板
}
int qry(int x){
return bit_qry(c1,x)*x-bit_qry(c2,x);//这里是树状数组区间加区间查询模板
}
void solve(int l,int r,int L,int R){
if(l>r||L>R)return;
if(L==R){
for(int i=l;i<=r;i++){
if(q[i].op==2){
res[q[i].id]=L;
}
}
return;
}
int mid=L+R>>1;
int cnt1=0,cnt2=0;
for(int i=l;i<=r;i++){
if(q[i].op==1){
if(q[i].c<=mid){
q1[++cnt1]=q[i];
}
else{
modify(q[i].l,1);
modify(q[i].r+1,-1);
q2[++cnt2]=q[i];
}
}
else{
int sum=qry(q[i].r)-qry(q[i].l-1);
if(q[i].c<=sum){
q2[++cnt2]=q[i];
}
else{
q[i].c-=sum;
q1[++cnt1]=q[i];
}
}
}
for(int i=l;i<=r;i++){
if(q[i].op==1){
if(q[i].c>mid){
modify(q[i].l,-1);
modify(q[i].r+1,1);
}
}
}
for(int i=1;i<=cnt1;i++){
q[i+l-1]=q1[i];
}
for(int i=1;i<=cnt2;i++){
q[i+l+cnt1-1]=q2[i];
}
solve(l,l+cnt1-1,L,mid);
solve(l+cnt1,r,mid+1,R);
}
signed main(){
cin>>n>>m;
int cnt=0;
for(int i=1;i<=m;i++){
cin>>q[i].op>>q[i].l>>q[i].r>>q[i].c;
if(q[i].op==2)q[i].id=++cnt;
}
solve(1,m,0,n);
for(int i=1;i<=cnt;i++){
if(res[i]==0)res[i]=-1;
cout<<res[i]<<'\n';
}
return 0;
}
小马和路
本题可以使用
考虑对于每条边,二分一个值,为如果以这条边作为最小边权的边加入所选边集,那么使得
发现二分
我们首先把待处理的边权倒序排序,然后每次使用可撤销并查集维护信息(不能路径压缩,故使用按秩合并),每次只需要扫一遍加边,判断加完边之后
代码:
#include<bits/stdc++.h>
#define int long long
#define N 200005
#define pii pair<int,int>
#define x first
#define y second
using namespace std;
int n,m,fa[N],siz[N];
int q[N],q1[N],q2[N],res[N];
pii e[N];
stack<pii>s;
int find(int x){
return fa[x]==x?x:find(fa[x]);
}
void merge(int x,int y){
x=find(x);y=find(y);
if(x==y)return;
if(siz[x]<siz[y])swap(x,y);
fa[y]=x;
siz[x]+=siz[y];
s.push({x,y});
}
void undo(){
int x=s.top().x,y=s.top().y;
s.pop();
siz[x]-=siz[y];
fa[y]=y;
}
void solve(int l,int r,int L,int R){
if(l>r||L>R)return;
if(L==R){
for(int i=l;i<=r;i++){
res[q[i]]=L;
}
return;
}
int mid=L+R>>1;
int cnt1=0,cnt2=0;
int now=mid+1;
for(int i=r;i>=l;i--){
if(q[i]>mid)q2[++cnt2]=q[i];
else{
while(now>q[i]){
now--;
merge(e[now].x,e[now].y);
}
if(find(1)==find(n))q1[++cnt1]=q[i];
else q2[++cnt2]=q[i];
}
}
while(!s.empty())undo();
reverse(q1+1,q1+cnt1+1);
reverse(q2+1,q2+cnt2+1);
for(int i=1;i<=cnt1;i++)q[i+l-1]=q1[i];
for(int i=1;i<=cnt2;i++)q[i+l-1+cnt1]=q2[i];
solve(l,l+cnt1-1,L,mid);
solve(l+cnt1,r,mid+1,R);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>e[i].x>>e[i].y;
q[i]=i;
}
for(int i=1;i<=n;i++){
fa[i]=i;
siz[i]=1;
}
solve(1,m,1,m+1);
int mn=2e18;
for(int i=1;i<=m;i++){
if(res[i]==m+1)continue;
mn=min(mn,res[i]-i+1);
}
if(mn==2e18)mn=-1;
cout<<mn;
return 0;
}
作业
矩阵乘法
首先考虑显然第
首先使用经典技巧,用下标代替权值。每次我们二分一个值
-
,归到左边。 -
否则先
,然后归到右边。
剩下的就是整体二分和二维树状数组的板子了,这里不多赘述。
混合果汁
首先还是先把
考虑二分如何进行判断。可以建立一棵以单价为下标的权值线段树,节点存储单价在
然后就是简单的了,每次只需要先判断总体积够不够。如果够,考虑权值线段树上二分,查找在体积够的情况下的最小花费(显然选单价更低的更优)。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】