目录
P2678 跳石头
这题一开始没读懂题,后来发现实际上是个类似贪心的思想
大意是说,每种移石子的方案都会有一个最小的距离,然后要求这其中的最大距离
也就是喜闻乐见的最小值最大
暴力枚举每种方案的复杂度是O(n^m),铁定会挂
我们自然会想到枚举每种答案再判断是否合法
而二分就是一种优化枚举答案次数的算法,复杂度O(nlogn),十分优秀
具体做法是二分答案,然后判断
由于读入是单调的,就省去了排序
判断的具体过程就是看这个解是否合法,简单来说就是模拟跳石子的过程看看是否符合题意
如果合法,考虑到答案不一定最优,继续往下找
如果不合法,由于序列是单调递增的,因此当前不合法后面的一定也不合法,因此往前找
这里给出上一行的证明
不合法说明撤掉的石子m1>m才能使答案成立,如果答案更大,则需要更大的m2>=m1,所以m2显然>m,不合法
这里其实还有个贪心的思想
可以把前缀和排序,然后尽量移除最小的(类似合并果子的操作),然而复杂度是爆炸的
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int const maxn=60000;
int a[maxn];
int d,n,m,l,r,ans;
int judge(int ans)
{
int next=1,now=0,cnt=0;
while(next<=n+1)
{
if(a[next]-a[now]<ans)
cnt++;
else
now=next;
next++;
}
if(cnt>m)
return 0;
return 1;
}
int main()
{
scanf("%d%d%d",&d,&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
a[n+1]=d;
l=1,r=d;
while(l<=r)
{
int mid=(l+r)>>1;
if(judge(mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
printf("%d",ans);
return 0;
}
好了我总算知道二分答案的真谛了= =
所谓二分答案其实是二分最优的满足某种性质的值(最优&&可行),然后验证他是否符合这种性质即可,比如最小值最大就是二分最大,验证是否符合最小
综上所述,能进行二分答案的条件
: 答案可以验证(比如模拟等等)
: 答案范围小(满足nlogn的复杂度)
CodeVS 2072 分配房间
这题和跳石头还不一样= =
这题必须要先选一个
因此边界处理不同
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int const maxn=1000101;
long long n,m,l,r,a[maxn],ans;
int check(int ans)
{
int last=1,cnt=m-1;
for(int i=2;i<=n;i++)
{
if(a[i]-a[last]>=ans)
{
cnt--;
last=i;
}
}
if(cnt<=0)
return 1;
return 0;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
}
sort(a+1,a+1+n);
l=0,r=a[n]-a[1];
//在这出了锅
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
printf("%lld",ans);
return 0;
}
好了二分答案又一关键:初始化、边界问题
这个无法总结,具体题目具体分析吧
CodeVs 1725探险
这题没啥好说的,一眼就看出来怎么做了
就是边界调了好久…
还是手模靠谱
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int const maxn=1000110;
int n,m,a[maxn],prs[maxn],l,r,ans;
inline int check(int ans)
{
int cnt=0,last=0;
for(int i=1;i<=n;i++)
if(prs[i]-prs[last]>=ans)
{
cnt++;
last=i;
}
if(cnt>=m)
return 1;
return 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
prs[i]=a[i]+prs[i-1];
// printf("%d\n",prs[i]);
}
l=0,r=prs[n];
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))
{
ans=mid;
l=mid+1;
}
else
r=mid-1;
}
printf("%d",ans);
return 0;
}
//又又又栽在初始化上了QAQ,以后绝对不能臆想了,还是手模靠谱...
P1083 借教室
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int const maxn=1000110;
int n,m,a[maxn],dlt[maxn],x[maxn],y[maxn],l,r,c[maxn],ans;
inline int check(int h)
{
memset(c,0,sizeof(c));//每次都要把差分数组还原
int sum=0;
for(int i=1;i<=h;i++)
{
c[x[i]]+=dlt[i];
c[y[i]+1]-=dlt[i];//边界问题
}
for(int i=1;i<=n;i++)
{
sum+=c[i];
// prs[i]=prs[i-1]+c[i];
//我曾经是这么写的,但总感觉不对
//好吧我蠢了
//prs[1]=prs[0]+c[1]就相当于初始化了==
if(sum>a[i])
return 0;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=m;i++)
scanf("%d%d%d",&dlt[i],&x[i],&y[i]);
l=0,r=m;
if(check(r))
{
printf("0");
return 0;
}
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid))
l=mid+1;
else
{
ans=mid;
r=mid-1;
}
}
printf("-1\n%d",ans);
return 0;
}
//答案到底是mid还是mid+1傻傻分不清楚==
//好吧是我自己的锅,ans应该在订单超出时赋值
P1314 聪明的质检员
首先,这道题的题目叫 想要摸鱼却却又缺乏智商的质检员 ,这个质检员想要摸鱼,却不知道怎么做,于是写了道题来求诸诸佬~~(然而又不能丑化自己,所以就叫聪明的质检员)~~
这道题思路很好想
由于w越大y越小,因此符合单调性
很容易想到二分w,根据w与y的关系来判断向哪个方向二分
具体细节看代码
#include<iostream>
#include<cstdio>
typedef long long ll;
//这道题要开long long
using namespace std;
int const maxn=2000100;
ll const maxx=9000000000000000000;
//long long 的上界比1e19小一点
int n,m,x[maxn],y[maxn],w[maxn],v[maxn];
int mn=0x3f3f3f3f,mx,l,r;
ll prsv[maxn],prsn[maxn],sum,c,ans=maxx,s;
inline int check(int ans)
{
sum=0;
for(int i=1;i<=n;i++)
{
if(w[i]>=ans)
prsv[i]=prsv[i-1]+v[i],prsn[i]=prsn[i-1]+1;
else
prsv[i]=prsv[i-1],prsn[i]=prsn[i-1];
}
//这里是利用前缀和来维护区间大于等于k的值
for(int i=1;i<=m;i++)
sum+=(prsv[y[i]]-prsv[x[i]-1])*(prsn[y[i]]-prsn[x[i]-1]);
c=llabs(sum-s);
if(sum>s)
return 1;
return 0;
}
int main()
{
scanf("%d%d%lld",&n,&m,&s);
for(int i=1;i<=n;i++)
{
scanf("%d%d",&w[i],&v[i]);
mx=max(w[i],mx);
mn=min(w[i],mn);
}
for(int i=1;i<=m;i++)
scanf("%d%d",&x[i],&y[i]);
l=mn-1,r=mx+2;
//这里是代码的精髓之处,虽然出题人并没有特意卡,但边界条件一定要想到
//二分有个性质,可以到达左界,但无法达到右界
//而这道题只是让选一个w,并没有指定范围
//因此就有三种情况,在w[i]的范围内、小于和大于
//因此mn-1指小于,mn~mx+1指在范围内,mn+2指大于
while(l<=r)
{
int mid=l+r>>1;
if(check(mid))
l=mid+1;
else
r=mid-1;
ans=min(ans,c);
//这里因为二分的并不是最终的答案,哨兵一下即可
}
printf("%lld",ans);
return 0;
}
P1525 关押罪犯
最大值最小,满足二分性质
分属的两个监狱可以看做二分图
如果满足二分图则一定可以完美分配
我们可以二分答案,将小于答案的边删去重新建图,如果能建立二分图,说明答案应该比当前答案小,如果不能建立,说明当前答案可行
PS:答案应是不可行的(即无法建二分图的),因此其实是最小值最大!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
int const maxn=210000,maxm=1001100;
struct E
{
int to,next,w;
E(int to=0,int next=0,int w=0):
to(to),next(next),w(w){}
}e[maxm<<1];
int head[maxn],vis[maxn],color[maxn];
int n,m;
int cnt,ans,maxx;
void add(int u,int v,int w)
{
e[++cnt]=(E){v,head[u],w};
head[u]=cnt;
}
void readin()
{
scanf("%d%d",&n,&m);
for(int x,y,z,i=1;i<=m;i++)
scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z),maxx=std::max(maxx,z);
}
int check(int min)
{
std::queue<int>q;
memset(color,0,sizeof(color));
for(int i=1;i<=n;i++)
if(!color[i])
{
q.push(i),color[i]=1;
while(!q.empty())
{
int u=q.front();q.pop();
for(int j=head[u];j!=-1;j=e[j].next)
{
int v=e[j].to,w=e[j].w;
if(w<min)
continue;
if(!color[v])
color[v]=color[u]==1?2:1,q.push(v);
else
if(color[u]==color[v])
return false;
}
}
}
return true;
}
int main()
{
memset(head,-1,sizeof(head));
readin();
int l=0,r=maxx+1;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid))
r=mid-1;
else
{
l=mid+1;
ans=mid;
}
}
printf("%d",ans);
return 0;
}