嗯…这是一个难以开口的专题,不算难,通常是和其他的算法一起呃干一些难以描述的事?使用这个的前提是某一个东西具有单调性而且就是在单调闭区间中进行的。呃分为二分答案和正常二分两种?呃啊其实也差的不算太多,一个可能维护的是序列中的位置,另一个可能维护的是两个数中的某一个值?思路的话通常会将询问等操作,化成判断,枚举一个值看其是否成立,呃啊其实也就不是很难,做多了就懂了。
板子的话要背这个,不然太多太杂了:
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
看一个!P2678 [NOIP2015 提高组] 跳石头来,给大家整个活:首先这个可以用贪心,不过嘛,欸,我选择更难的二分答案,那就要看判断函数啦。我们枚举一个可行的最短跳跃距离的最大值。那么小于这个的石头就要删除掉,并且令cnt++,若cnt>m 那么就说明不可行咯。好!好活当赏!呜呜呜我哭了难受啊。时间的话O(nlogn)吧。
#include<bits/stdc++.h>
using namespace std;
int n,m,L,ans;
int a[500001];
bool check(int lim)
{
int cnt=0,s=0;
for(int i=1;i<=n+1;i++)
{
if(lim>a[i]-s) cnt++;
else s=a[i];
}
if(cnt<=m) return true;
else return false;
}
int main()
{
scanf("%d%d%d",&L,&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
a[n+1]=L;int l=0,r=L;
while(l<=r) //好的枚举!
{
int mid=(l+r)/2;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d",ans);
return 0;
}
下一道也差不多吧P3853 [TJOI2007]路标设置这个和上面那道差不多啦,说的好啊嗯。
#include<bits/stdc++.h>
using namespace std;
int n,m,L,ans;
int a[1000001];
bool check(int lim)//好的更新方式
{
int cnt=0,s=0;
for(int i=1;i<=n;i++)
{
while(a[i]-s>lim) s+=lim,cnt++;
s=max(s,a[i]);
}
if(cnt>m) return true;
else return false;
}
int main()
{
scanf("%d%d%d",&L,&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
int l=0,r=L;
while(l<=r)//枚举的是区间最小最大长度
{
int mid=(l+r)/2;
if(check(mid)) l=mid+1;
else ans=mid,r=mid-1;
}
printf("%d",ans);
return 0;
}
下一题:P1281 书的复制呃这个的话一个小二分不说了,心痛破防累,没啥的,大概,这是,合理的:
#include<bits/stdc++.h>
using namespace std;
int n,m,l,r,ans;
int st[100001],ed[100001],a[100001];
bool check(int lim)
{
int cnt=1,k=0;
for(int i=1;i<=n;i++)
{
if(k+a[i]>lim) k=0,cnt++;
k+=a[i];
}
if(cnt>m||k>lim) return false;
else return true;
}
int main()
{
memset(st,0,sizeof(st));memset(ed,0,sizeof(ed));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]),r+=a[i];
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
int now=n;
for(int i=m;i>=1;i--)
{
int k=0;ed[i]=now;
while(k+a[now]<=ans&&now>=1) st[i]=now,k+=a[now--];
}
for(int i=1;i<=m;i++) printf("%d %d\n",st[i],ed[i]);
return 0;
}
有一道有小数数的二分,用我的板子不知道行不行,打打看看?P1570 KC 喝咖啡呃啊有点破防问题在于这道题没什么问题:
#include<bits/stdc++.h>
using namespace std;
int n,m;
double l,r,ans;
struct node
{
double v,c,dis;
};node e[100001];
bool cmp(const node &x,const node &y)
{
return x.dis<y.dis;
}
bool check(double lim)
{
double tot=0;
for(int i=1;i<=n;i++) e[i].dis=(e[i].c*lim)-e[i].v;
sort(e+1,e+n+1,cmp);
for(int i=1;i<=m;i++) tot+=e[i].dis;
if(tot<=0) return true;
else return false ;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lf",&e[i].v);
for(int i=1;i<=n;i++) scanf("%lf",&e[i].c),r=max(r,e[i].v/e[i].c);
while(l<=r)
{
double mid=(l+r)/2;
if(check(mid)) l=mid+0.00001;
else r=mid-0.00001,ans=mid;
}
printf("%.3lf",ans);
return 0;
}
下一个吧P1577 切绳子嗯差不多的,不过有一个小小技巧,因为这个只要求答案保留到小数点后 2位(直接舍掉 2 位后的小数)。所以我们可以将这个转化为整数,后面再除二不过没什么用:
#include<bits/stdc++.h>
using namespace std;
int n,m;
double L[1000001];
int a[1000001];
int l=0,r=10000001,ans;
bool check(int lim)
{
int cnt=0;
for(int i=1;i<=n;i++) cnt+=a[i]/lim;
if(cnt>=m) return true ;
else return false ;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lf",&L[i]),a[i]=(int)(L[i]*100.0);
while(l<=r)
{
int mid=(l+r)/2;
if(mid==0)
{
ans=mid;
break;
}
if(check(mid)) l=mid+1,ans=mid;
else r=mid-1;
}
printf("%.2lf",ans/100.0);
return 0;
}
其实也没什么的,也就这样了,再做一两道题也就行了吧P1843 奶牛晒衣服:
#include<bits/stdc++.h>
using namespace std;
int n,m,a,b,ans;
int w[600001];
bool check(int lim)
{
int cnt=0;
for(int i=1;i<=n;i++)
{
int k=w[i]-lim*a;
if(k>0)
{
if(k%b==0) cnt+=(k/b);
else cnt+=(k/b)+1;
}
}
if(cnt<=lim) return true ;
else return false ;
}
int main()
{
scanf("%d%d%d",&n,&a,&b);
int l=0,r=0;
for(int i=1;i<=n;i++) scanf("%d",&w[i]),r=max(r,w[i]/a+1);
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
printf("%d",ans);
return 0;
}
下一题P2370 yyy2015c01 的 U 盘呃本来是dp+二分的不过后面发现可以直接dp,因为数据好像挺水,导致dp可以直接过哦。
#include<bits/stdc++.h>
using namespace std;
int n,m,p,s;
int w[100001],v[100001];
struct node
{
int w,v;
};node e[100001];
bool cmp(const node &x,const node &y)
{
return x.w<y.w;
}
int f[10001];
int main()
{
scanf("%d%d%d",&n,&p,&s);
for(int i=1;i<=n;i++) scanf("%d%d",&e[i].w,&e[i].v);
sort(e+1,e+n+1,cmp);
for(int i=1;i<=n;i++)
{
for(int j=s;j>=e[i].w;j--)
{
f[j]=max(f[j],f[j-e[i].w]+e[i].v);
if(f[j]>=p)
{
printf("%d",e[i].w);
return 0;
}
}
}
printf("No Solution!");
return 0;
}
那二分就写到这吧,不算难的东西就放下了再打一会儿倍增。好吧做倍增做到一道二分:P4403 [BJWC2008]秦腾与教学评估,考虑从奇偶数性入手,那么发现性质:奇数加偶数是奇数,偶数加偶数是偶数,那么既然题目保证了唯一性,就直接考虑用前缀和维护若一个点是奇数,则它后面的点都是,那么再套上一个二分答案就可以解决了。打一下?
//我们这题直接维护的是前缀和,所以我们可以直接拿这
//(min(lim,e[i])-s[i])/d[i]+1; 来维护一手1到lim的前缀和 注意要把一开始的点加上
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,t,ans;
int s[400000],e[400000],d[400000];
bool check(int lim)
{
int sum=0;
for(int i=1;i<=n;i++)
{
if(s[i]<=lim) sum+=(min(lim,e[i])-s[i])/d[i]+1;//s起始点与e边界咯,然后求出区间长度内的站着的人
}
if(sum%2==1) return true ;
else return false;
}
signed main()
{
scanf("%lld",&t);
while(t--)
{
int l=1e9,r=0,pd=0,sum=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld%lld",&s[i],&e[i],&d[i]);
l=min(l,s[i]);r=max(r,e[i]);//最大的左右端点
pd+=(e[i]-s[i])/d[i]+1;
}
if(pd%2==0)
{
printf("Poor QIN Teng:(\n");
continue ;
}
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)) ans=mid,r=mid-1;
else l=mid+1;
}
for(int i=1;i<=n;i++)
{
if(ans<s[i]||ans>e[i]) continue ;
if((ans-s[i])%d[i]==0) sum++;//若这个点刚好在s[i]上或者d[i]+s[i]上
}
printf("%lld %lld\n",ans,sum);
}
return 0;
}
wqs二分学习笔记:
https://www.luogu.com.cn/blog/Flying2018/wqs-er-fen-min-ke-fu-si-ji-hu-xue-xi-bi-ji
https://www.luogu.com.cn/blog/daniu/wqs-er-fen
对于一个形如二次函数求最大值的东西,即其拥有单调性,我们可以使用wqs二分 二分斜率。
具体而言,对于一些给定的数值,在一定限制条件下,求解最大值。P5633 最小度限制生成树,这题的问题在于给定了一些边,并要求其中有一点有且仅有k条边,明显可以看出其有单调性。不妨尝试转化成wqs,最后再处理一些不合法情况即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,fa[1000001],len=0,s,k,check1=0;
struct pp
{
int x,y,w,s;
};pp p[1000001];
int findfa(int x)
{
if(fa[x]==x) return x;
return fa[x]=findfa(fa[x]);
}
void ins(int x,int y,int w)
{
int ss=0,now=++len;if(x==s||y==s) ss=1;
p[now]={x,y,w,ss};check1+=ss;
return ;
}
bool merge(int x,int y)
{
int xx=findfa(x),yy=findfa(y);if(xx==yy) return false;
fa[xx]=yy;return true;
}
bool cmp(const pp &x,const pp &y)
{
if(x.w==y.w) return x.s>y.s;
return x.w<y.w;
}
void remake(int val)
{
for(int i=1;i<=len;i++) if(p[i].s) p[i].w+=val;
return ;
}
bool check2()
{
int now=n-1,now2=0;for(int i=1;i<=n;i++) fa[i]=i;remake(1e9);sort(p+1,p+m+1,cmp);
for(int i=1;i<=m;i++)
{
if(!merge(p[i].x,p[i].y)) continue ;
now--,now2+=p[i].s;//printf("%d %d %d %d\n",p[i].x,p[i].y,now2,p[i].s);
}remake(-1e9);//printf("%d %d ",now2,k);
if((!now)&&now2<=k) return true;
return false;
}
int check(int val)
{
remake(val);for(int i=1;i<=n;i++) fa[i]=i;int now=n-1,sumval=0,now2=0;sort(p+1,p+m+1,cmp);
for(int i=1;i<=m;i++)
{
int x=p[i].x,y=p[i].y;if(!merge(x,y)) continue ;
now--,sumval+=p[i].w;if(x==s||y==s) now2++;
if(!now) break ;
}
remake(-val);
if(now||now2<k) return -1;return sumval;
}
signed main()
{
scanf("%lld%lld%lld%lld",&n,&m,&s,&k);
for(int i=1;i<=m;i++)
{
int x,y,w;scanf("%lld%lld%lld",&x,&y,&w);ins(x,y,w);
}
if(check1<k||!check2())
{
// printf("%d %d %d\n",check1,k,check2);
printf("Impossible\n");return 0;
}
int l=-10000000ll,r=10000000ll,ans=-10000000ll;
if(check(l)==-1)
{
printf("Impossible\n");
return 0;
}
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)!=-1) ans=max(ans,mid),l=mid+1;
else r=mid-1;
}
int sum=check(ans);
printf("%lld",sum-ans*k);
return 0;
}
P2619 [国家集训队] Tree I,这题是上面的简化版,同样也是使用wqs的算法。
#include<bits/stdc++.h>
using namespace std;
int n,m,need,fa[100001];
struct pp
{
int x,y,c,color;
};pp p[1000001];
int findfa(int x)
{
if(fa[x]==x) return x;
return fa[x]=findfa(fa[x]);
}
int check()
{
int tot=0,num=0,rt=0;
for(int i=1;i<=m;i++)
{
int x=p[i].x,y=p[i].y,fx=findfa(x),fy=findfa(y);
if(fx!=fy) rt+=p[i].c,tot+=(p[i].color^1),fa[fx]=fy,num++;
if(num==n-1)break;
}
if(tot>=need) return rt;
return 0;
}
bool cmp(const pp &x,const pp &y)
{
if(x.c==y.c) return x.color<y.color;
return x.c<y.c;
}
void remake(int val)
{
for(int i=0;i<=n;i++) fa[i]=i;
for(int i=1;i<=m;i++) if(!p[i].color) p[i].c+=val;
sort(p+1,p+m+1,cmp);
return ;
}
int main()
{
scanf("%d%d%d",&n,&m,&need);
int l=-111,r=111,ans;
for(int i=1;i<=m;i++) scanf("%d%d%d%d",&p[i].x,&p[i].y,&p[i].c,&p[i].color);//l-=min(p[i].c,0),r+=max(0,p[i].c);
while(l<=r)
{
int mid=(l+r)/2;
remake(mid);
if(check()) ans=mid,l=mid+1;
else r=mid-1;
remake(-mid);
}
remake(ans);
printf("%d",check()-ans*need);
return 0;
}