YbtOJ 「基础算法」 第3章 二分算法
二分
A. 【例题1】数列分段
[题目描述]
对于给定的一个长度为N的正整数数列
关于最大值最小:
例如一数列
将其如下分段:
第一段和为
将其如下分段:
第一段和为
并且无论如何分段,最大值不会小于
所以可以得到要将数列
[输入格式]
第
第
[输出格式]
一个正整数,即每段和最大值最小为多少。
[算法分析]
看到最大值最小 显然想到二分 二分的一般策略是二分一个答案 用check函数来判断正确性 再决定缩小上界还是下界
上界初值显然是所有数的和(只分一段)
下界初值就是所有数的最大值(每一个数都分一段)
最后的答案就是l
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int n , m , a[N] , l , r , ans;
int check ( int mid )
{
int cnt = 1 , sum = 0;
for ( int i = 1 ; i <= n ; i ++ )
{
if ( sum + a[i] > mid ) cnt ++ , sum = 0;
sum += a[i];
}
return m < cnt;//<如果为真 则不可行 否则可行
}
signed main ()
{
n = read() , m = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , l = max ( l , a[i] ) , r += a[i];
while ( l <= r )
{
int mid = l + r >> 1;
if ( check ( mid ) ) l = mid + 1;
else r = mid - 1;
}
printf ( "%d" , l );
return 0;
}
B. 【例题2】防具布置
[题目描述]
现在有
[输入格式]
第一行是一个整数
每组数据的第一行是一个整数
之后
[输出格式]
对于每组测试数据,如果防线没有破绽,输出一行 There's no weakness.
。
否则在一行内输出两个空格分隔的整数
[算法分析]
开long long很有必要
首先 因为保证了只有一个分界点 那么我们二分这个分界点
如果1-mid段上的防具数量之和是偶数 那么收缩下界 如果是奇数 说明1-mid这一段中有那个分界点 收缩上界
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5;
const int inf = INT_MAX;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int n , l , r;
struct node { int s , e , d; } a[N];
//s是防具起始位置,e是防具结束位置,d是防具间间隔,
int check ( int mid )//获取到1-mid区间内防具数量之和
{
int ans = 0;
for ( int i = 1 ; i <= n ; i ++ )
{
if ( a[i].s <= mid ) ans += ( ( min ( a[i].e , mid ) - a[i].s ) / a[i].d ) + 1;
else break;
}
return ans;
}
signed main ()
{
int T = read();
while ( T -- )
{
n = read();
for ( int i = 1 ; i <= n ; i ++ )
a[i].s = read() , a[i].e = read() , a[i].d = read();
l = 0 , r = inf;
if ( check(inf) % 2 == 0 ) { printf ( "There's no weakness.\n" ); continue; }
sort ( a + 1 , a + n + 1 , [](const node a , const node b) { return a.s < b.s; } );
while ( l <= r )
{
int mid = l + r >> 1;
if ( check(mid) % 2 == 0 ) l = mid + 1;
else r = mid - 1;
}
printf ( "%lld %lld\n" , l , check(l) - check(l-1) );
}
return 0;
}
C. 【例题3】最大均值
[题目描述]
给定正整数序列
[输入格式]
第一行两个整数
接下来
[输出格式]
输出一个整数,表示平均值的最大值乘以
[算法分析]
考虑二分平均值答案
对于每一次查询 先将所有数组减去这个平均值再 查询(1)--(k)(1<=k<=i-m)中所有前缀和的最小值用来转移 每一次用这个点的前缀和减去这个最小值判断最大子段 只要有一个子段大于等于0 则考虑收缩下界 如果所有子段都不能大于这个mid平均值 则考虑收缩上界
实数域二分 考虑向下取整 所以用r作为答案
[代码实现]
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid ((l+r)/2)
const int N = 1e6 + 5;
const int inf = 0x3f3f3f3f;
const double eps = 1e-6;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , L;
double l = inf , r = -inf , a[N] , b[N] , sum[N];
int check ( double x )
{
for ( int i = 1 ; i <= n ; i ++ ) b[i] = a[i] - x;
for ( int i = 1 ; i <= n ; i ++ ) sum[i] = sum[i-1] + b[i];
double ans = -inf , minn = inf;
for ( int i = L ; i <= n ; i ++ )
{
minn = min ( minn , sum[i-L] );
ans = max ( ans , sum[i] - minn );
}
return ans > 0;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , L = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read() , l = min ( l , a[i] ) , r = max ( r , a[i] );
while ( r - l >= eps )//二分这个平均值
{
if ( check(mid) ) l = mid;
else r = mid;
}
cout << (int)floor(r*1000) << endl;
return 0;
}
D. 1.喂养宠物
[题目描述]
兔兔是可爱的动物,小明想拥有一些。
宠物店提供
[输入格式]
第一行两个整数
第二行
第三行
[输出格式]
一个整数,表示小明最多可以养多少只兔兔。
[算法分析]
二分一个兔兔最大值 每一次统计需要的食物总量 并与
[代码实现]
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define mid ((l+r)>>1)
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int n , k;
struct node { int hun , gre , res; }a[N];
int check ( int x )
{
int ans = 0;
for ( int i = 1 ; i <= n ; i ++ ) a[i].res = a[i].hun + a[i].gre * ( x - 1 );
sort ( a + 1 , a + n + 1 , [](const node &x , const node &y) { return x.res < y.res; } );
for ( int i = 1 ; i <= x ; i ++ ) ans += a[i].res;
return ans <= k;
}
signed main ()
{
n = read() , k = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].hun = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].gre = read();
int l = 0 , r = n;
while ( l <= r )
{
if ( check(mid) ) l = mid + 1;
else r = mid - 1;
}
printf ( "%d" , r );
return 0;
}
E. 2.最小时间
[题目描述]
有
当前处于时刻
给出
[输入格式]
第一行三个整数
接下来
[输出格式]
一行一个整数表示答案。
[算法分析]
我们二分一个最大距离
[代码实现]
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
#define mid ((l+r)>>1)
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int n , m , s;
struct node { int k , b , tot; } a[N];
//对于每一个时间 排序一下 选出前m个价值最大的值是绝对不劣的
int check ( int x )
{
for ( int i = 1 ; i <= n ; i ++ ) a[i].tot = a[i].k * x + a[i].b;
nth_element ( a + 1 , a + m + 1 , a + n + 1 , [](const node &a , const node &b) { return a.tot > b.tot; } );
int ans = 0;
for ( int i = 1 ; i <= m ; i ++ )
{
if ( a[i].tot < 0 ) continue;
ans += a[i].tot;
if ( s <= ans ) return true;
}
return s <= ans;
}
signed main ()
{
n = read() , m = read() , s = read();
for ( int i = 1 ; i <= n ; i ++ ) a[i].k = read() , a[i].b = read();
if ( check (0) ) { cout << "0" << endl; return 0; }
int l = 0 , r = 1e9 + 5;
while ( l <= r )
{
if ( check (mid) ) r = mid - 1;
else l = mid + 1;
}
printf ( "%lld" , l );
return 0;
}
F. 3.攻击法坛
[题目描述]
有一个魔法阵。
魔法阵可以看作一条直线,有些位置上筑有法坛,一共有
你现在要摧毁魔法阵上的
法杖的神奇之处在于,
[输入格式]
第一行三个整数
接下来
[输出格式]
只有一个整数,表示
[算法分析]
之后二分一个
[代码实现]
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define mid ((l+r)>>1)
const int N = 2e3 + 5;
inl int read()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int n , m1 , m2 , a[N] , k1[N] , k2[N] , f[N][N];
//k1[i] 表示在第 i 个位置用普通法杖最远可以覆盖到的最远点的下标
//k2[i] 就是用高级法杖
//f[i][j] 表示用 i 个普通法杖和 j 个高级法杖能到的(所有特殊点都覆盖)的最远点
int check ( int x )
{
memset ( k1 , 0 , sizeof k1 );
memset ( k2 , 0 , sizeof k2 );
memset ( f , 0 , sizeof f );
for ( int i = 1 , j ; i <= n ; i ++ )
{
int q1 = a[i] + x - 1 , q2 = a[i] + 2 * x - 1;
//法杖从a[i]出发最远能覆盖的点的位置
j = i;
while ( q1 >= a[j] && j <= n ) j ++; //最远能覆盖的点的下标
k1[i] = j - 1;
j = i;
while ( q2 >= a[j] && j <= n ) j ++;
k2[i] = j - 1;
}
k1[n + 1] = k2[n + 1] = n;
for ( int i = 0 ; i <= m1 ; i ++ )
for ( int j = 0 ; j <= m2 ; j ++ )
{
if ( i > 0 ) f[i][j] = max ( f[i][j] , k1[f[i-1][j]+1] );
if ( j > 0 ) f[i][j] = max ( f[i][j] , k2[f[i][j-1]+1] );
}
return f[m1][m2] == n;
}
signed main ()
{
n = read() , m1 = read() , m2 = read();//一级法杖的使用次数 二级法杖的使用次数
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
if ( m1 + m2 >= n ) { printf ( "1" ); return 0; }
sort ( a + 1 , a + n + 1 );
int l = 0 , r = 1e9;
while ( l <= r )
{
if ( check (mid) ) r = mid - 1;
else l = mid + 1;
}
printf ( "%d" , l );
return 0;
}
G. 4.跳石头
[题目描述]
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。
[输入格式]
第一行包含三个整数
接下来
[输出格式]
一个整数,即最短跳跃距离的最大值。
[算法分析]
[代码实现]
#include<bits/stdc++.h>
using namespace std;
#define inl inline
#define mid ((l+r)>>1)
const int N = 1e6 + 5;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
return x * f;
}
int n , m , L , a[N];
int check ( int x )
{
int ans = 0 , now = 0;
for ( int i = 1 ; i <= n ; i ++ )
{
if ( a[i] - a[now] < x ) ans ++;//去掉
else now = i;//不去掉
}
return ans <= m;
}
signed main ()
{
L = read() , n = read() , m = read();
//起点到终点的距离 起点和终点之间的岩石数量 以及组委会至多移走的岩石数量
for ( int i = 1 ; i <= n ; i ++ ) a[i] = read();
sort ( a + 1 , a + n + 1 );
a[n+1] = L;
int l = 0 , r = L;//二分最大距离
while ( l <= r )
{
if ( check ( mid ) ) l = mid + 1;
else r = mid - 1;
}
printf ( "%d" , r );
return 0;
}
H. 5.飞离地球
[题目描述]
你现在要从标号为
某一些星球之间有航线,由于超时空隧道的存在,从一个星球到另一个星球时间可能会倒流,而且,从星球
宇宙法规定:" 禁止在出发时间前到达目的地。"
每艘飞船上都有速度调节装置,可以调节飞行的时间。其功能可以使得整次航程中所有两星球间的飞行时间增加或减少相同的整数值。你的任务是帮助它调整速度调节器,找出一条最短时间到达目的地的路径。
[输入格式]
输入文件包含多组数据,第一个数为
对于每组数据,输入第一行为两个正整数
[输出格式]
输出文件共
如果可以通过调节速度调节器完成任务,则输出一个非负整数,表示由星球 到星球 的最短时间。
如果不能由星球 到达星球 ,则输出 -1。
#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define mid ((l+r)>>1)
const int N = 1e3 + 5;
const int inf = 0x3f3f3f3f;
inl int read ()
{
int x = 0 , f = 1;
char ch = getchar();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar(); }
return x * f;
}
int n , m , flag , vis[N][N];
int cnt , head[N];
struct node { int to , nxt , w; } e[N*N];
void add ( int u , int v , int w ) { e[++cnt] = { v , head[u] , w }; head[u] = cnt; }
int q[N*N] , dis[N] , in[N] , hh , tt , cntt[N];
int spfa ( int s , int d )
{
for ( int i = 1 ; i <= n ; i ++ ) dis[i] = inf , in[i] = 0 , cntt[i] = 0;
hh = 1 , tt = 0 , dis[s] = 0;
q[++tt] = s , in[s] = 1 , cntt[s] = 1;
while ( hh <= tt )
{
int u = q[hh++]; in[u] = 0;
for ( int i = head[u] ; i ; i = e[i].nxt )
{
int v = e[i].to;
if ( dis[v] > dis[u] + e[i].w + d && vis[v][n] )
{
cntt[v] = cntt[u] + 1;
dis[v] = dis[u] + e[i].w + d;
if ( cntt[v] > n ) return 1;
if ( !in[v] ) q[++tt] = v , in[v] = 1;
}
}
}
return dis[n] < 0;//????
}
void init()
{
flag = cnt = 0;
for ( int i = 1 ; i <= n ; i ++ ) head[i] = 0;
for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) vis[i][j] = 0;
}
int floyd()
{
for ( int i = 1 ; i <= n ; i ++ ) vis[i][i] = 1;
for ( int k = 1 ; k <= n ; k ++ )
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ )
vis[i][j] = vis[i][j] | ( vis[i][k] & vis[k][j] );
}
signed main ()
{
int T = read();
while ( T -- )
{
n = read() , m = read(); init();
for ( int i = 1 , u , v , w ; i <= m ; i ++ ) u = read() , v = read() , w = read() , add ( u , v , w ) , vis[u][v] = 1;
floyd();
if ( !vis[1][n] ) { printf ( "-1\n" ); continue; }
int l = -1000005 , r = 1000005;
while ( l <= r )//二分一个每条边增加或减少的时间
{
if ( spfa ( 1 , mid ) ) l = mid + 1;
else r = mid - 1;
}
spfa ( 1 , l );//再跑一遍
printf ( "%d\n" , dis[n] );
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现