题面传送门
算法简介:二分查找(答案),时间复杂度
O
(
l
o
g
2
n
)
O(log^2n)
O(log2n).可以在一个有序表中快速查找某个数。可以利用这个性质来二分答案来做到快速有效枚举。
算法实现:对于一个有序表,二分查找一个数是否存在。
首先定义边界值:
l
=
0
l=0
l=0,
r
=
m
a
x
(
a
i
)
+
1
r=max(a_{i})+1
r=max(ai)+1;然后取中值
m
i
d
=
(
l
+
r
)
2
mid=\frac{(l+r)}{2}
mid=2(l+r);
这是一个有序表,满足
a
i
−
1
≤
a
i
≤
a
i
+
1
a_{i-1}\leq a_i\leq a_{i+1}
ai−1≤ai≤ai+1或
a
i
−
1
≥
a
i
≥
a
i
+
1
a_{i-1}\geq a_i\geq a_{i+1}
ai−1≥ai≥ai+1。
判断中点是否大于待查找数。若大于,则收拢左边界。反之则收拢右边界。则一定能找到待查找值是否存在。
复杂度:
l
o
g
2
(
m
a
x
(
a
i
)
+
1
)
log^2(max(a_i)+1)
log2(max(ai)+1)
个人理解:挺有用的,但有局限性,必须在有序表中查询。
代码实现:
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,a[100039],x,y,flag,l,r,mid;
inline void read(int &x){
char s=getchar(); int f=1;x=0;
while(s<'0'||s>'9'){if(s=='-') f=-1;s=getchar();}
while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+(s^48),s=getchar();
x*=f;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) read(a[i]);
sort(a+1,a+n+1);
while(m--){
flag=0;
read(x);
l=1;r=n;
while(l<r){
mid=(l+r)>>1;
if(a[mid]==x||a[l]==x||a[r]==x){printf("YES\n");flag=1;break;}
if(a[mid]>x) r=mid-1;
else if(a[mid]<x) l=mid+1;
}
if(!flag) printf("NO\n");
}
return 0;
}
二分应用:
1
1
1:二分答案基础题(好吧第一次打这道题用了一个小时):
对于这道题,我们用
d
a
t
a
i
data_i
datai表示每小段长度为
i
i
i时能切割出的小段数。
则
d
a
t
a
i
data_i
datai可以表示为
∑
k
=
1
n
⌊
a
k
i
⌋
\sum\limits_{k=1}^{n}{\left\lfloor\dfrac{a_k}{i}\right\rfloor }
k=1∑n⌊iak⌋ 则当
i
<
j
<
k
i<j<k
i<j<k时,
d
a
t
a
i
≥
d
a
t
a
j
≥
d
a
t
a
k
data_i\geq data_j\geq data_k
datai≥dataj≥datak。满足二分答案的性质。
所以可以直接二分然后验证。对于答案小于
m
m
m,收拢右边界,反之收拢左边界。
代码实现:
#include<cstdio>
using namespace std;
int n,m,a[100039],l,r,mid,ans,flag;
int main(){
register int i;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++) scanf("%d",&a[i]);
l=0;r=100000001;
while(l+1<r){
mid=(l+r)>>1;
ans=0;
for(i=1;i<=n;i++) ans+=a[i]/mid;
if(ans<m) r=mid;
else l=mid;
}
printf("%d",l);
}
2
2
2:二分答案普及题(好吧第一次打这道题用了
n
n
n个小时,
n
>
2
n>2
n>2):
我们发现,当
i
<
j
i<j
i<j时,当i订单不满足要求时,j订单一定不满足要求。所以这是一个二分找
01
01
01交接点的问题。
当我们二分答案时可以借助差分数组验证。
代码实现:
#include<bits/stdc++.h>
using namespace std;
int n,m,s[1000001],d[1000001],q[1000001],a[1000001],b[1000001],c[1000001],l,r,mid,flag;
int main() {
//freopen("1.in","r",stdin);
scanf("%d%d",&n,&m);
for(register int i=1;i<=n;i++) scanf("%d",&s[i]);
for(register int i=1;i<=m;i++)scanf("%d%d%d",&a[i],&b[i],&c[i]);
l=0,r=m+1;
while(l+1<r){
flag=0;
memset(d,0,sizeof(d));
mid=(l+r)>>1;
for(register int i=1;i<=mid;i++) d[b[i]]+=a[i],d[c[i]+1]-=a[i];
for(register int i=1;i<=n;i++){
d[i]+=d[i-1];
if(d[i]>s[i]){flag=1;break;}
}
if(flag) r=mid;
else l=mid;
}
if(l==m) printf("0");
else printf("-1\n%d",r);
return 0;
}