文章目录
二分\尺取\三分及练习题
1. 数的范围(二分+模板)
#include <iostream>
using namespace std;
const int maxn = 100005;
int n, q, x, a[maxn];
int main() {
scanf("%d%d", &n, &q);
for (int i = 0; i < n; i++) scanf("%d", &a[i]);
while (q--) {
scanf("%d", &x);
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r >> 1;
if (a[mid] < x) l = mid + 1;
else r = mid;
}
/* 寻找第一个x */
if (a[l] != x) {
printf("-1 -1\n");
continue;
}
/* 如果第一个数不是x,那么这次循环提前结束 */
int l1 = l, r1 = n;
while (l1 + 1 < r1) {
int mid = l1 + r1 >> 1;
if (a[mid] <= x) l1 = mid;
else r1 = mid;
}
/* 找到最后一个x */
printf("%d %d\n", l, l1);
}
return 0;
}
一个包含重复元素的有序序列,要求输出某元素出现的起始位置和终止位置,翻译一下就是:在数组中查找某元素,找不到就输出-1,找到了就输出不小于该元素的最小位置和不大于该元素的最大位置。所以,需要写两个二分,一个需要找到 ≥ x \geq x ≥x的第一个数,另一个需要找到 ≤ x \leq x ≤x的最后一个数
二分模板
:
查找不小于x的第一个位置
:
int l = 0, r = n - 1;
while (l < r) {
int mid = l + r >> 1;
if (a[mid] < x) l = mid + 1;
else r = mid;
}
当a[mid]小于x时,令l = mid + 1,mid及其左边的位置被排除了,可能出现解的位置是mid + 1及其后面的位置;当a[mid] >= x时,说明mid及其左边可能含有值为x的元素;当查找结束时,l与r相遇,l所在元素若是x则一定是x出现最小位置,因为l左边的元素必然都小于x。
查找不大于x的最后一个位置
:
修改区间开闭性
:
int l1 = l, r1 = n;
while (l1 + 1 < r1) {
int mid = l1 + r1 >> 1;
if (a[mid] <= x) l1 = mid;
else r1 = mid;
}
修改mid取整方向
:
int l = 0, r = n - 1;
while (l < r)
{
int mid = l + r + 1 >> 1;
if (a[mid] <= x) l = mid;
else r = mid - 1;
}
两种方式都不修改
:
int l1 = l, r1 = n - 1;
while (l1 < r1) {
int mid = l1 + r1 >> 1;
if (a[mid] <= x) l1 = mid + 1;
else r1 = mid - 1;
}
printf("%d %d\n", l, l1 - (a[l1] == x ? 0 : 1));
因为第一轮已经找到了一个等于 x x x的位置。所以完全可以当 a [ m i d ] < = x a[mid] <= x a[mid]<=x时,令 l = m i d + 1 l = mid + 1 l=mid+1,此时, l l l指向的元素可能是 x x x也可能比 x x x大,但是由于不论大小, l l l和 r r r的指针都移动了,就不会陷入死循环了,最后,如果 a [ l ] = = x a[l] == x a[l]==x则,l就是x出现的最后的位置,否则, l − 1 l - 1 l−1就是 x x x出现的最后一个位置。
2. Hamburgers(二分转判定)
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
typedef long long ll;
ll B,S,C;
ll nb,ns,nc;
ll pb,ps,pc;
ll R;
ll check(ll mid){
ll xx=mid*B; ll yy=mid*S; ll zz=mid*C;
xx=max(xx-nb,0ll);yy=max(yy-ns,0ll);zz=max(zz-nc,0ll);
return xx*pb+yy*ps+zz*pc;
}
int main(){
string s;
cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]=='B') B++;
else if(s[i]=='S') S++;
else C++;
}
/* 做一个特定的汉堡需要的原料 */
cin>>nb>>ns>>nc;
cin>>pb>>ps>>pc;
cin>>R;
ll l=0,r=1e13;
while(l<r){
ll mid=l+r+1>>1;
if(check(mid)<=R)
l=mid;
else r=mid-1;
}
/* 找到最优解 */
cout<<l;
}
3. Subsequence(尺取法)
POJ No.3061 《挑战程序设计竞赛》P146
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAX_N 100005
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
LL a[MAX_N];
int n, t, ans = INF;
LL sum, s;
int main() {
while (scanf("%d%d",&n,&s)!=EOF) {
for (int i = 0; i < n; i++) scanf("%I64d", a + i);
int start = 0, end = 0;
ans = INF;
sum = 0;
while (1) {
while (end < n && sum < s) sum += a[end++];
if (sum < s) break;
/* 整个数列都不大于s */
ans = min(ans, end - start);
sum -= a[start++];
/* 尺取一次之后如果满足>=s则将左端点右移动 */
}
if (ans == INF) ans = 0;
printf("%d\n", ans);
}
return 0;
}
解决本问题应该满足数组元素均为非负的
4. 三分法模板
凸函数函数先增再减
:
整数范围
:
int SanFen(int l,int r) //找凸点
{
while(l < r-1)
{
int mid = (l+r)/2;
int mmid = (mid+r)/2;
if( f(mid) > f(mmid) )
r = mmid;
else
l = mid;
}
return f(l) > f(r) ? l : r;
}
实数范围
:
double three_devide(double low,double up)
{
double m1,m2;
while(up-low>=eps)
{
m1=low+(up-low)/3;
m2=up-(up-low)/3;
if(f(m1)<=f(m2))
low=m1;
else
up=m2;
}
return (m1+m2)/2;
}
凸函数先减再增
:
整数范围
:
int SanFen(int l,int r) //找凸点
{
while(l < r-1)
{
int mid = (l+r)/2;
int mmid = (mid+r)/2;
if( f(mid) > f(mmid) )
l = mid;
else
r = mmid;
}
return f(l) > f(r) ? l : r;
}
实数范围
:
double three_devide(double low,double up)
{
double m1,m2;
while(up-low>=eps)
{
m1=low+(up-low)/3;
m2=up-(up-low)/3;
if(f(m1)<=f(m2))
up=m2;
else
low=m1;
}
return (m1+m2)/2;
}
5. 数的三次方根
#include <cstdio>
int main(){
double x;
scanf("%lf",&x);
double l=-1000,r=1000;
while(r-l>1e-8){
double mid=(l+r)/2;
if(mid*mid*mid>=x){
r=mid;
}else{
l=mid;
}
}
printf("%lf",l);
return 0;
}
6. [NOIP 2011提高组] 聪明的质监员
#include<cstdio>
#include<iostream>
using namespace std;
const int N=200010;
int w[N],v[N],l[N],r[N];
long long pre_n[N],pre_v[N];
long long Y,s,sum;
int n,m,mx=-1,mn=2147483647;
bool check(int W)
{
Y=0,sum=0;
memset(pre_n,0,sizeof(pre_n));
memset(pre_v,0,sizeof(pre_v));
for(int i=1;i<=n;i++)
{
if(w[i]>=W) pre_n[i]=pre_n[i-1]+1,pre_v[i]=pre_v[i-1]+v[i]; /* 前缀和优化 */
else pre_n[i]=pre_n[i-1],pre_v[i]=pre_v[i-1];
}
for(int i=1;i<=m;i++)
Y+=(pre_n[r[i]]-pre_n[l[i]-1])*(pre_v[r[i]]-pre_v[l[i]-1]); /* 三次一起处理 */
sum=llabs(Y-s);
if(Y>s) return true;
else return false;
}
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(mx,w[i]);
mn=min(mn,w[i]);
}
for(int i=1;i<=m;i++)
scanf(" %d %d",&l[i],&r[i]);
int left=mn-1,right=mx+1,mid;
/* 设定区间,减少运行时间 */
long long ans=0x3f3f3f3f3f3f3f3f;
while(left<right)
{
mid=(left+right)>>1;
if(check(mid)) left=mid+1; /* 让下一次mid增大,从而满足y的单调性,使得y逐渐减小,靠近s */
else right=mid; /* 同样满足单调性,使得下一次mid减小,逐步将y从小于s的状态转移至接近s的状态,即:使y逐步增大 */
if(sum<ans) ans=sum;
}
printf("%lld",ans);
return 0;
}
7. [NOIP2012 提高组] 借教室
本人第一次做法,读入优化之后只能通过一半示例
#include <iostream>
#include <vector>
using namespace std;
int n,m;
int r[1000011],d[1000011],s[1000011],t[1000011];
int ans=0;
inline int read(){
char c=getchar();
int f=1;
int x=0;
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x*f;
}
void check(){
for(register int i=1;i<=m;i++){
for(register int j=s[i];j<=t[i];j++){
r[j]-=d[i];
if(r[j]<0){
ans=i;
return;
}
}
}
return;
}
int main(){
n=read();m=read();
for(register int i=1;i<=n;i++){
r[i]=read();
}
for(register int i=1;i<=m;i++){
d[i]=read();s[i]=read();t[i]=read();
}
check();
if(ans){
cout<<-1<<'\n'<<ans;
}else{
cout<<0;
}
}
差分+前缀和+二分搜索
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m;
int diff[1000011],need[1000011],rest[1000011],r[1000011],l[1000011],d[1000011];
bool isok(int x)
{
memset(diff,0,sizeof(diff));
for(int i=1;i<=x;i++)
{
diff[l[i]]+=d[i];
diff[r[i]+1]-=d[i];
}
for(int i=1;i<=n;i++)
{
need[i]=need[i-1]+diff[i];
if(need[i]>rest[i])return 0;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&rest[i]);
for(int i=1;i<=m;i++)scanf("%d%d%d",&d[i],&l[i],&r[i]);
int begin=1,end=m;
if(isok(m)){cout<<"0";return 0;}
while(begin<end)
{
int mid=(begin+end)/2;
if(isok(mid))begin=mid+1;
else end=mid;
}
cout<<"-1"<<endl<<begin;
}
8. [SHOI2015]自动刷题机
#include<cstdio>
#define int long long
using namespace std;
int n,k,a[100005];
int l,r,ans;
int check(int x)
{
int sum=0,len=0;
for(int i=1;i<=n;i++)
{
sum+=a[i];
if(sum<0) sum=0;
if(sum>=x) sum=0,len++;
}
return len;
}
signed main()
{
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
l=1,r=1e15;
ans=1e15+1;
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)>k) l=mid+1;
else if(check(mid)<k) r=mid-1;
else ans=mid,r=mid-1;
}
if(ans!=1e15+1) printf("%lld ",ans);
else
{
printf("-1");
return 0;
}
l=1,r=1e15;
ans=1e15+1;
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid)>k) l=mid+1;
else if(check(mid)<k) r=mid-1;
else ans=mid,l=mid+1;
}
printf("%lld\n",ans);
return 0;
}