Part2.3二分答案

对一个满足单调性质的问题,我们可以采用二分答案的方法来解决。

[NOIP2001 提高组] 一元三次方程求解

题目描述

有形如: a x 3 + b x 2 + c x + d = 0 a x^3 + b x^2 + c x + d = 0 ax3+bx2+cx+d=0 这样的一个一元三次方程。给出该方程中各项的系数( a , b , c , d a,b,c,d a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在 − 100 -100 100 100 100 100 之间),且根与根之差的绝对值 ≥ 1 \ge 1 1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 2 2 位。

提示:记方程 f ( x ) = 0 f(x) = 0 f(x)=0,若存在 2 2 2 个数 x 1 x_1 x1 x 2 x_2 x2,且 x 1 < x 2 x_1 < x_2 x1<x2 f ( x 1 ) × f ( x 2 ) < 0 f(x_1) \times f(x_2) < 0 f(x1)×f(x2)<0,则在 ( x 1 , x 2 ) (x_1, x_2) (x1,x2) 之间一定有一个根。

输入格式

一行, 4 4 4 个实数 a , b , c , d a, b, c, d a,b,c,d

输出格式

一行, 3 3 3 个实根,从小到大输出,并精确到小数点后 2 2 2 位。

样例 #1

样例输入 #1

1 -5 -4 20

样例输出 #1

-2.00 2.00 5.00

提示

【题目来源】

NOIP 2001 提高组第一题

代码实现

#include<iostream>
using namespace std;
double a,b,c,d;
double f(double x){return a*x*x*x+b*x*x+c*x+d;}
void solve(double x,double y)
{
	int flag=0;
	while(y-x>0.001)
	{
		double mid=(x+y)/2;
		if(f(mid)==0)
		{
			printf("%.2lf ",mid);
			flag=1;
			break;
		}
		if(f(x)*f(mid)<0)y=mid;
		else if(f(mid)*f(y)<0)x=mid;
	}
	if(!flag)printf("%.2lf ",x);
	return ;
}
int main()
{
	cin>>a>>b>>c>>d;
	for(double i=-100;i<=99;i++)
	{
		if(f(i)==0)printf("%.2lf ",i);
		else if(f(i)*f(i+1)<0)solve(i,i+1.0);
	}
	if(f(100)==0)printf("%.2lf ",100);
	return 0;
}

[NOIP2015 提高组] 跳石头

题目背景

NOIP2015 Day2T1

题目描述

一年一度的“跳石头”比赛又要开始了!

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有 N N N 块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走 M M M 块岩石(不能移走起点和终点的岩石)。

输入格式

第一行包含三个整数 L , N , M L,N,M L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 L ≥ 1 L \geq 1 L1 N ≥ M ≥ 0 N \geq M \geq 0 NM0

接下来 N N N 行,每行一个整数,第 i i i 行的整数 D i   ( 0 < D i < L ) D_i\,( 0 < D_i < L) Di(0<Di<L), 表示第 i i i 块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

样例 #1

样例输入 #1

25 5 2 
2
11
14
17 
21

样例输出 #1

4

提示

输入输出样例 1 说明

将与起点距离为 2 2 2 14 14 14 的两个岩石移走后,最短的跳跃距离为 4 4 4(从与起点距离 17 17 17 的岩石跳到距离 21 21 21 的岩石,或者从距离 21 21 21 的岩石跳到终点)。

数据规模与约定

对于 20 % 20\% 20%的数据, 0 ≤ M ≤ N ≤ 10 0 \le M \le N \le 10 0MN10
对于 50 % 50\% 50% 的数据, 0 ≤ M ≤ N ≤ 100 0 \le M \le N \le 100 0MN100
对于 100 % 100\% 100% 的数据, 0 ≤ M ≤ N ≤ 50000 , 1 ≤ L ≤ 1 0 9 0 \le M \le N \le 50000,1 \le L \le 10^9 0MN50000,1L109

代码实现

#include<iostream>
using namespace std;
#define MAX_N 50000
int a[MAX_N+5];
int l,m,n;
bool check(int x)
{
	int cnt=n,now=1;
	for(int i=2;i<=m+1;i++)
	{
		if(a[i]-a[now]<x)cnt--;
		else now=i;
		if(cnt==-1)return 0;
	}
	if(a[m+2]-a[now]<x)cnt--;
	if(cnt==-1)return 0;
	return 1;
}
int main()
{
	cin>>l>>m>>n;
	a[1]=0;
	for(int i=2;i<=m+1;i++)scanf("%d",&a[i]);
	a[m+2]=l;
	int head=1,tail=l,mid;
	while(head<tail)
	{
		int mid=(head+tail+1)/2;
		if(check(mid))head=mid;
		else tail=mid-1;
	}
	cout<<head;
	return 0;
}

刺杀大使

题目描述

某组织正在策划一起对某大使的刺杀行动。他们来到了使馆,准备完成此次刺杀,要进入使馆首先必须通过使馆前的防御迷阵。

迷阵由 n × m n\times m n×m 个相同的小房间组成,每个房间与相邻四个房间之间有门可通行。在第 n n n 行的 m m m 个房间里有 m m m 个机关,这些机关必须全部打开才可以进入大使馆。而第 1 1 1 行的 m m m 个房间有 m m m 扇向外打开的门,是迷阵的入口。除了第 1 1 1 行和第 n n n 行的房间外,每个房间都被使馆的安保人员安装了激光杀伤装置,将会对进入房间的人造成一定的伤害。第 i i i 行第 j j j 列 造成的伤害值为 p i , j p_{i,j} pi,j(第 1 1 1 行和第 n n n 行的 p p p 值全部为 0 0 0)。

现在某组织打算以最小伤害代价进入迷阵,打开全部机关,显然,他们可以选 择任意多的人从任意的门进入,但必须到达第 n n n 行的每个房间。一个士兵受到的伤害值为他到达某个机关的路径上所有房间的伤害值中的最大值,整个部队受到的伤害值为所有士兵的伤害值中的最大值。现在,这个恐怖组织掌握了迷阵的情况,他们需要提前知道怎么安排士兵的行进路线可以使得整个部队的伤害值最小。

输入格式

第一行有两个整数 n , m n,m n,m,表示迷阵的大小。

接下来 n n n 行,每行 m m m 个数,第 i i i 行第 j j j 列的数表示 p i , j p_{i,j} pi,j

输出格式

输出一个数,表示最小伤害代价。

样例 #1

样例输入 #1

4 2
0 0 
3 5 
2 4 
0 0

样例输出 #1

3

提示

  • 50 % 50\% 50% 的数据, n , m ≤ 100 n,m \leq 100 n,m100
  • 100 % 100\% 100% 的数据, n , m ≤ 1000 n,m \leq 1000 n,m1000 p i , j ≤ 1000 p_{i,j} \leq 1000 pi,j1000

代码实现

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
#define MAX_N 1000
int p[MAX_N+5][MAX_N+5],vis[MAX_N+5][MAX_N+5];	
int n,m,min_val=1000,max_val=0;
int dx[5]={0,1,0,-1,0};
int dy[5]={0,0,1,0,-1};
bool bfs(int val)
{
	queue<pair<int,int>>q;
	vis[1][1]=1;
	q.push(make_pair(1,1));
	while(!q.empty())
	{
		int x=q.front().first;
		int y=q.front().second;
		q.pop();
		for(int i=1;i<=4;i++)
		{
			if(x+dx[i]<1||x+dx[i]>n||y+dy[i]<1||y+dy[i]>m||vis[x+dx[i]][y+dy[i]]||p[x+dx[i]][y+dy[i]]>val)continue;
			vis[x+dx[i]][y+dy[i]]=1;
			if(x+dx[i]==n)return 1;
			else q.push(make_pair(x+dx[i],y+dy[i]));
		}
	}
	return 0; 
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	for(int j=1;j<=m;j++)
	{
		cin>>p[i][j];
		min_val=min(min_val,p[i][j]);
		max_val=max(max_val,p[i][j]);
	}
	int head=min_val,tail=max_val,mid;
	while(head<tail)
	{
		memset(vis,0,sizeof(vis));
		mid=(head+tail)/2;
		if(bfs(mid))tail=mid;
		else head=mid+1;
	}
	cout<<head;
	return 0;
}

[NOIP2011 提高组] 聪明的质监员

题目描述

小T 是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有 n n n 个矿石,从 1 1 1 n n n 逐一编号,每个矿石都有自己的重量 w i w_i wi 以及价值 v i v_i vi 。检验矿产的流程是:

  1. 给定$ m$ 个区间 [ l i , r i ] [l_i,r_i] [li,ri]
  2. 选出一个参数 W W W
  3. 对于一个区间 [ l i , r i ] [l_i,r_i] [li,ri],计算矿石在这个区间上的检验值 y i y_i yi

y i = ∑ j = l i r i [ w j ≥ W ] × ∑ j = l i r i [ w j ≥ W ] v j y_i=\sum\limits_{j=l_i}^{r_i}[w_j \ge W] \times \sum\limits_{j=l_i}^{r_i}[w_j \ge W]v_j yi=j=liri[wjW]×j=liri[wjW]vj

其中 j j j 为矿石编号。

这批矿产的检验结果 y y y 为各个区间的检验值之和。即: ∑ i = 1 m y i \sum\limits_{i=1}^m y_i i=1myi

若这批矿产的检验结果与所给标准值 s s s 相差太多,就需要再去检验另一批矿产。小T 不想费时间去检验另一批矿产,所以他想通过调整参数 W W W 的值,让检验结果尽可能的靠近标准值 s s s,即使得 ∣ s − y ∣ |s-y| sy 最小。请你帮忙求出这个最小值。

输入格式

第一行包含三个整数 n , m , s n,m,s n,m,s,分别表示矿石的个数、区间的个数和标准值。

接下来的 n n n 行,每行两个整数,中间用空格隔开,第 i + 1 i+1 i+1 行表示 i i i 号矿石的重量 w i w_i wi 和价值 v i v_i vi

接下来的 m m m 行,表示区间,每行两个整数,中间用空格隔开,第 i + n + 1 i+n+1 i+n+1 行表示区间 [ l i , r i ] [l_i,r_i] [li,ri] 的两个端点 l i l_i li r i r_i ri。注意:不同区间可能重合或相互重叠。

输出格式

一个整数,表示所求的最小值。

样例 #1

样例输入 #1

5 3 15 
1 5 
2 5 
3 5 
4 5 
5 5 
1 5 
2 4 
3 3

样例输出 #1

10

提示

【输入输出样例说明】

W W W 4 4 4 的时候,三个区间上检验值分别为 20 , 5 , 0 20,5 ,0 20,5,0 ,这批矿产的检验结果为 25 25 25,此时与标准值 S S S 相差最小为 10 10 10

【数据范围】

对于 $10% $ 的数据,有 1 ≤ n , m ≤ 10 1 ≤n ,m≤10 1n,m10

对于 $30% $的数据,有 1 ≤ n , m ≤ 500 1 ≤n ,m≤500 1n,m500

对于 $50% $ 的数据,有 $ 1 ≤n ,m≤5,000$;

对于 70 % 70\% 70% 的数据,有 1 ≤ n , m ≤ 10 , 000 1 ≤n ,m≤10,000 1n,m10,000

对于 100 % 100\% 100% 的数据,有 $ 1 ≤n ,m≤200,000$, 0 < w i , v i ≤ 1 0 6 0 < w_i,v_i≤10^6 0<wi,vi106 0 < s ≤ 1 0 12 0 < s≤10^{12} 0<s1012 1 ≤ l i ≤ r i ≤ n 1 ≤l_i ≤r_i ≤n 1lirin

代码实现

#include<iostream>
using namespace std;
#define MAX_N 200000
int n,m;
int w[MAX_N+5],v[MAX_N+5],l[MAX_N+5],r[MAX_N+5];
long long sum1[MAX_N+5],sum2[MAX_N+5];
long long calc(int W)
{
	for(int i=1;i<=n;i++)
	{
		sum1[i]=sum1[i-1]+(w[i]>=W);
		sum2[i]=sum2[i-1]+(w[i]>=W)*v[i];
	}
	long long y=0;
	for(int i=1;i<=m;i++)
	{
		long long yi=(sum1[r[i]]-sum1[l[i]-1])*(sum2[r[i]]-sum2[l[i]-1]);
		y+=yi;
	}
	return y;
}
int main()
{
	long long s;
	cin>>n>>m>>s;
	sum1[0]=0,sum2[0]=0;
	int head=0,tail=0,mid;
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&w[i],&v[i]);
		tail=max(tail,w[i]);
	}
	for(int i=1;i<=m;i++)
	scanf("%d%d",&l[i],&r[i]);
	long long ans=s;
	while(head<=tail)
	{
		mid=(head+tail)/2;
		long long temp_y=calc(mid);
		ans=min(ans,abs(temp_y-s));
		if(temp_y>s)head=mid+1;
		else if(temp_y<s)tail=mid-1;
		else break;
	}
	cout<<ans;
	return 0;
}


[NOIP2012 提高组] 借教室

题目描述

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。

我们需要处理接下来 n n n 天的借教室信息,其中第 i i i 天学校有 r i r_i ri 个教室可供租借。共有 m m m 份订单,每份订单用三个正整数描述,分别为 d j , s j , t j d_j,s_j,t_j dj,sj,tj,表示某租借者需要从第 s j s_j sj 天到第 t j t_j tj 天租借教室(包括第 s j s_j sj 天和第 t j t_j tj 天),每天需要租借 d j d_j dj 个教室。

我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供 d j d_j dj 个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第 s j s_j sj 天到第 t j t_j tj 天中有至少一天剩余的教室数量不足 d j d_j dj 个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

输入格式

第一行包含两个正整数 n , m n,m n,m,表示天数和订单的数量。

第二行包含 n n n 个正整数,其中第 i i i 个数为 r i r_i ri,表示第 i i i 天可用于租借的教室数量。

接下来有 m m m 行,每行包含三个正整数 d j , s j , t j d_j,s_j,t_j dj,sj,tj,表示租借的数量,租借开始、结束分别在第几天。

每行相邻的两个数之间均用一个空格隔开。天数与订单均用从 1 1 1 开始的整数编号。

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数 0 0 0。否则(订单无法完全满足)

输出两行,第一行输出一个负整数 − 1 -1 1,第二行输出需要修改订单的申请人编号。

样例 #1

样例输入 #1

4 3 
2 5 4 3 
2 1 3 
3 2 4 
4 2 4

样例输出 #1

-1 
2

提示

【输入输出样例说明】

第 $1 $份订单满足后,$4 $天剩余的教室数分别为 0 , 3 , 2 , 3 0,3,2,3 0,3,2,3。第 2 2 2 份订单要求第 $2 $天到第 4 4 4 天每天提供$ 3 $个教室,而第 3 3 3 天剩余的教室数为$ 2$,因此无法满足。分配停止,通知第 2 2 2 个申请人修改订单。

【数据范围】

对于10%的数据,有 1 ≤ n , m ≤ 10 1≤ n,m≤ 10 1n,m10

对于30%的数据,有 1 ≤ n , m ≤ 1000 1≤ n,m≤1000 1n,m1000

对于 70%的数据,有 1 ≤ n , m ≤ 1 0 5 1 ≤ n,m ≤ 10^5 1n,m105

对于 100%的数据,有 1 ≤ n , m ≤ 1 0 6 , 0 ≤ r i , d j ≤ 1 0 9 , 1 ≤ s j ≤ t j ≤ n 1 ≤ n,m ≤ 10^6,0 ≤ r_i,d_j≤ 10^9,1 ≤ s_j≤ t_j≤ n 1n,m106,0ri,dj109,1sjtjn

NOIP 2012 提高组 第二天 第二题

2022.2.20 新增一组 hack 数据

代码实现

#include<iostream>
#include<cstring>
using namespace std;
#define MAX_N 1000000
int n,m;
long long diff[MAX_N+5],d[MAX_N+5],room[MAX_N+5];
int s[MAX_N+5],t[MAX_N+5];
bool check(int x)
{
	memset(diff,0,sizeof(diff));
	for(int i=1;i<=x;i++)
	{
		diff[s[i]]+=d[i];
		diff[t[i]+1]-=d[i];
	}
	long long sum=0;
	for(int i=1;i<=n;i++)
	{
		sum+=diff[i];
		if(sum>room[i])return 0;
	}
	return 1;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)scanf("%lld",&room[i]);
	for(int i=1;i<=m;i++)scanf("%lld%d%d",&d[i],&s[i],&t[i]);
	int head=0,tail=m,mid;
	if(check(m)){
		cout<<0;
		return 0;
	}
	while(head<tail)
	{
		mid=(head+tail+1)/2;
		if(check(mid))head=mid;
		else tail=mid-1;
	}
	cout<<-1<<endl<<head+1;
	return 0;
 } 

[SHOI2015] 自动刷题机

题目背景

曾经发明了信号增幅仪的发明家 SHTSC 又公开了他的新发明:自动刷题机——一种可以自动 AC 题目的神秘装置。

题目描述

自动刷题机刷题的方式非常简单:首先会瞬间得出题目的正确做法,然后开始写程序。每秒,自动刷题机的代码生成模块会有两种可能的结果:

1.写了 x x x 行代码
2.心情不好,删掉了之前写的 y y y 行代码。(如果 y y y 大于当前代码长度则相当于全部删除。)

对于一个 OJ,存在某个固定的正整数长度 n n n,一旦自动刷题机在某秒结束时积累了大于等于 n n n 行的代码,它就会自动提交并 AC 此题,然后新建一个文件(即弃置之前的所有代码)并开始写下一题。SHTSC 在某个 OJ 上跑了一天的自动刷题机,得到了很多条关于写代码的日志信息。他突然发现自己没有记录这个 OJ 的 n n n 究竟是多少。所幸他通过自己在 OJ 上的 Rank 知道了自动刷题机一共切了 k k k 道题,希望你计算 n n n 可能的最小值和最大值。

输入格式

第一行两个整数 l , k l , k l,k,表示刷题机的日志一共有 l l l 行,一共了切了 k k k 题。

接下来 l l l 行,每行一个整数 x i x_i xi,依次表示每条日志。若 x i ≥ 0 x_i \geq 0 xi0,则表示写了 x i x_i xi 行代码,若 x i < 0 x_i \lt 0 xi<0,则表示删除了 − x i -x_i xi 行代码。

输出格式

输出一行两个整数,分别表示 n n n 可能的最小值和最大值。
如果这样的 n n n 不存在,请输出一行一个整数 − 1 -1 1

样例 #1

样例输入 #1

4 2
2
5
-3
9

样例输出 #1

3 7

提示

数据规模与约定
  • 对于 20 % 20\% 20% 的数据,保证 l ≤ 10 l \le 10 l10
  • 对于 40 % 40\% 40% 的数据,保证 l ≤ 100 l \le 100 l100
  • 对于 60 % 60\% 60% 的数据,保证 l ≤ 2 × 1 0 3 l \le 2 \times 10^3 l2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ l ≤ 1 0 5 1 \leq l \le 10^5 1l105 − 1 0 9 ≤ x i ≤ 1 0 9 -10^9 \le x_i \le 10^9 109xi109

代码实现

#include<iostream>
using namespace std;
int l,k;
#define MAX_L 100000
long long jilu[MAX_L+5];
int check(long long x,int flag)
{
	long long a=0;
	long long now=0,cnt=0;
	for(int i=1;i<=l;i++)
	{
		now+=jilu[i];
		now=max(now,a);
		if(now>=x)
		{
			cnt++;
			now=0;
		}
	}
	if(cnt==k)return 2;
	if(flag)return cnt>k;
	return cnt<k;
}
int main()
{
	cin>>l>>k;
	for(int i=1;i<=l;i++)
	scanf("%lld",&jilu[i]);
	long long head=1,tail=100000000000000,mid;
	while(head<tail)
	{
		mid=(head+tail)/2;
		if(check(mid,0))tail=mid;
		else head=mid+1;
	}
	if(check(head,0)!=2){
		cout<<-1;
		return 0;
	}
	cout<<head<<" ";
	head=1,tail=100000000000000,mid;
	while(head<tail)
	{
		mid=(head+tail+1)/2;
		if(check(mid,1))head=mid;
		else tail=mid-1;
	}	
	cout<<head<<endl;
	return 0;
}
  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值