版权声明: https://blog.csdn.net/qq_40828060/article/details/82112253
目录
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应该在订单超出时赋值