ACM寒假集训专题二总结

噩梦般的二分法

Easy1:

#include <iostream>
using namespace std;

int main(){
	
	int n;
	cin>>n;
	int group[100000];
	for (int i = 0; i < n; i++){
		
		int a;
		cin>>a;
		group[i] = a;
		
	} 
	
	int q, x, ans, mid;
	cin>>q;
	int right = n-1;
	int left = 0;
	for (int j = 0; j < q; j++){
		
		cin>>x;
		while (right >= left){
			
			mid = (right+left)/2;
			ans = group[mid];
			
			if (group[mid] == x){
				
				cout<<"Yes\n";
				break;
				
			}
			else if (group[mid] > x) right = mid-1;	
			else if (group[mid] < x) left = mid+1;

		} 
			
		if (ans != x) cout<<"No\n";	
		left = 0;
		right = n-1;
			
	}
	
	return 0;
}

思路:

二分法入门第一题(最友好的一集) 不断地折半查找折半查找折半查找就找到答案了

Easy2:

题目跳转:

洛谷 - P1102 

#include <iostream>
#include <algorithm>
using namespace std;

int main(){
	
	int n;
	long long c, t;
	long long count = 0;
	long long a[200000];
	cin>>n>>c;
	
	for (int i = 0; i < n; i++){
		
		cin>>a[i];
		
	}
	sort(a, a+n);

	for (int j = 0; j < n; j++){
		
		long long* t1 = lower_bound(a+j, a+n, a[j]+c);
		long long* t2 = upper_bound(a+j, a+n, a[j]+c);
		
		if (t1 != t2) t = t2-t1;
		else t = 0;
		
		count += t;
	}
	
	cout<<count;
	
	return 0;
}

思路:

最开始想了一堆奇奇怪怪的办法 但是想来想去都觉得会超时 后来看了群里之前传的文件(关于STL的) 发现好像可以用 lower_bound 函数和 upper_bound 函数来解决这个问题 找到与当前元素相差 等于c的第一个元素 和 大于c的第一个元素 出现的位置 两者相减就得到当前元素与后面元素组合中符合 A-B=C 的数量 然后用循环遍历每个数 不断重复这个操作 就可以得到最终答案了

一度以为要超时了居然没有 二分法比我想象的要简便好多 :D

Medium1:

题目跳转:
 洛谷 - P8647 

#include <iostream>
#include <algorithm>
using namespace std;

int main(){
	
	int N, K;
	cin>>N>>K;
	int h[100000];
	int w[100000];
	int c[100000];
	int max = 0;
	
	for (int i = 0; i < N; i++){
		
		cin>>h[i]>>w[i];
		c[i] = min(h[i], w[i]);
		
		if (c[i] > max) max = c[i];
	}
	
	int count = 0, count1 = 0;
	
	int r = max;
	int l = 1;
	while(r >= l){
		
		int mid = (r+l)/2;
		for (int j = 0; j < N; j++){
			
			count += (h[j]/mid)*(w[j]/mid);
			
		}

		if (count < K){
			
			r = mid-1;
	
		}else if (count >= K){
			
			l = mid+1;
			count1 = mid;
			
		}
		count = 0;
		
	}
		
	cout<<count1;
	
	return 0;
}

思路:

既然这专题是二分法那思路就往二分上靠了

选择了将巧克力的边长作为我要二分的数据 范围从1~巧克力最短边长中最大的数 然后不断验证边长为mid时切出来的巧克力是否满足人数 如果发现切出来的巧克力数量小于人数 那就缩小mid的值 让边长减小 巧克力数量增加 反之增大mid的值 让切出来的巧克力在满足人数要求情况下边长尽可能的大

Medium2:

题目跳转:

洛谷 - P8800 

#include <iostream> 
#include <algorithm>
#define int long long
using namespace std;

struct Num{
	
	int a;
	int b;
	
}num[200000];

int n, m;

bool check(int mid){
	
	int amount = 0;
	for (int i = 0; i < n; i++){
		
		if (num[i].a + num[i].b < mid) return 1;
		if (num[i].a < mid) amount += mid - num[i].a;
		if (amount > m) return 1;
		
	}
	
	if (amount <= m){
			
		return 0;
			
	}
} 

bool cmp(struct Num&x, struct Num&y){
	
	return x.a < y.a;
	
}

signed main(){
	
	cin>>n>>m;
	for (int i = 0; i < n; i++){
		
		cin>>num[i].a;
		
	}
	for (int j = 0; j < n; j++){
		
		cin>>num[j].b;
		
	}
	sort(num, num+n, cmp);
	
	int r = num[n-1].a+num[n-1].b; 
	int l = 0;
	int mid;
	while(l <= r){
		
		mid = (l+r)/2;
		if (check(mid)) r = mid-1;
		else l = mid+1;
		
	}
	
	cout<<r;
	
	return 0;
}

思路:

(首先现学了一个结构体排序 orz)

假设一个答案 然后验证这个答案是否正确 (这就是二分答案的魅力吗) 如果当前数和它的可补充牌数小于我假设的答案 说明猜的答案太大了 要减小;如果当前牌数小于猜测的答案 就拿一个变量储存它们之间的差值 同时检验是否超过总的增加牌数 若是超过了 说明猜的答案还是太大 如果小于等于 说明我猜的答案还可以再大一些 就这样不断循环直到左边界大于等于右边界时输出右边界 即为最终答案

しかし 这里有个我当时非常疑惑的点 最开始我选择输出的值并不是右边界而是中间值mid 但是提交后WA一片(败北了)上网看到某位大佬代码后做了一些改动(将输出值改为右边界) 居然对了???更加摸不着头脑的我又进行了一些改动 (如下)

int r = num[n-1].a+num[n-1].b; 
	int l = 0;
	int mid;
	int ans;
	while(l <= r){
		
		mid = (l+r)/2;
		if (check(mid)) r = mid-1;
		else l = mid+1, ans = mid;
		
	}
	
	cout<<ans;

居然又对了???(我第一次这么写是错的→不过那会应该是check函数有问题)

是的 直到我写下这些文字时我仍然没有明白为什么这是怎么对的

(这里是写完小青蛙归来的我 我要说 我终于明白了 ( ̄ˇ ̄))

Hard1:

题目跳转:

洛谷 - P1281

#include <iostream> 
#include <algorithm>
using namespace std;
	
int m, k;
int a[500] = {0};

bool check(int mid){
	
	int amount = 0;
	int count = 0;
	for (int i = m-1; i >= 0; i--){
		
		if (amount+a[i] > mid){
			
			count++;
			amount = 0;
			
		}amount += a[i];
		
	}
	if (amount != 0) count++;
	if (count <= k) return 1;
	if (count > k) return 0;
	
}

int main(){

	cin>>m>>k;
	int num[500] = {0};
		
	int r = 0;
	for (int i = 0; i < m; i++){
		
		int x;
		cin>>x;
		a[i] = x;
		r += x;
		
	}
	
	int l = a[m-1]; 
	while (l < r){
		
		int mid = (r+l)/2;
		if (check(mid)) r = mid;
		else l = mid+1;
		
	}

	num[0] = m;
	int counter = 1;
	int sum1 = 0;
	for (int j = m-1; j >= 0; j--){
		
		if (sum1 + a[j] > r){
			
			num[counter] = j+2;
			num[counter+1] = j+1;

			sum1 = 0;
			counter += 2;
		}
		sum1 += a[j];
		
	} 
	if (sum1 != 0) num[counter] = 1; 
	
	for (int x = counter; x >= 0; x -= 2){
		
		cout<<num[x]<<" "<<num[x-1]<<endl;
		
	}
	
	return 0;
}

思路:

依旧是二分答案 不过不是自己想出来的罢了(

在洛谷看了其他大佬的题解 知道大概思路以后就开始自己琢磨着写出来 

答案范围是页数最大~总页数之和 那就在这中间去不断检验猜测的答案是否正确 因为要保证在总时长最短的情况下前面的人复制时间尽可能短 所以选择用倒序循环一一遍历 让每个人的复制时间都不超过猜测答案 如果超过就跳转到下一个人继续记录复制时间 另外还要记得加上最后一本书的复制时间(因为最后即使加上最后一本书的复制时间大概率也是小于等于猜测答案的 不加会导致二分的循环出不来) 接下来判断复制完所有书需要的人数是否等于题目给的人数 如果大了 左边界右移 增加猜测答案 反之右边界左移 减小猜测答案

得到最小用时后 就可以开始进行分配了 也是倒序循环 从页数最多的书开始分配 复制时间超过最小用时就转到下一个人继续记录时长 这里用一个新数组来储存跳转前后书的序号 最后只需要正序输出该数组就好了

不过这里我也有一个疑问 第47行为什么右边界要等于中间值才可以呢(思考)

while (l < r){
		
		int mid = (r+l)/2;
		if (check(mid)) r = mid;
		else l = mid+1;
		
	}

Hard2:

题目跳转:

洛谷 - P8775

#include <iostream> 
#include <algorithm>
#define int long long
using namespace std;

int n, x;
int a[100005] = {0};
int b[100005] = {0};

bool check(int mid){

	for (int i = mid; i < n; i++){
		
		int sum = 0;
		sum = b[i] - b[i-mid];
		
		if (sum < x*2) return 1;
		
	}

	return 0;
	
}

signed main(){

	cin>>n>>x;

	for (int i = 1; i < n; i++){
		
		cin>>a[i];
		b[i] = a[i] + b[i-1];
		
	}
	
	int r = n;
	int l = 1;
	while (l <= r){
		
		int mid = (r+l)/2;
		if (check(mid)) l = mid+1;
		else r = mid-1;
				
	}
	
	cout<<l;
	
	return 0;
}

思路:

同样是在洛谷上看了大佬的题解之后开始自己着手写 写的过程中又不断在参考大佬们的代码然后优化自己的

感觉核心思路就是将一只小青蛙的x次来回看作2x只小青蛙的单次过河 然后得出跳跃能力区间内可供落脚的石头数量必然大于等于2x 这样就可以用二分答案开始猜测 答案范围是1~河的宽度 在check函数中检验是否每个长度为mid(跳跃能力)的区间内都有大于等于2x的石头数量 如果不是 就说明要增加强化小青蛙的跳跃能力 如果是的话可以再减小看看 这就样不断缩小范围得到一个最终值

在写check函数时发现从头开始的循环很难把握好如何计算每个区间可供落脚石头之和 于是又学习了一下大佬们的写法:用新数组存储前面的石头之和 然后从mid开始循环新数组 用[i]-[i-mid]很轻松就能得到该区间石头之和 成功解决了问题 (超佩服的说)

总结:

和第一专题不太一样 感觉第二专题的难度翻了不止一倍(一点都不简单) 写代码的过程也很艰难 没有思路或者根本不知道怎么用上二分法 将思路转化为具体的代码表达出来的过程也是很大的挑战 磕磕绊绊地完成了第二专题的六道题 虽然写题过程有些疲惫 但是全部完成以后回头看 真的还是很有收获的 

好的 下一专题 我准备好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值