实验三-二分和分治

1.二分查找

The game of reporting numbers

描述

Tom is playing a game with his friends, and he is the referee of the game. First, Tom assigns each friend a number, and all the numbers are different. For each of the next rounds, Tom will tell a number, and the person with the largest number that does not exceed this number must report his own number. If nobody's number is smaller than the number told by Tom, then the person with the smallest number must report his own number. Everyone can repeat reporting his number. After the game is over, Tom will give a sequence of the numbers reported in turn by his friends. Please compute the sequence and output it.

输入

The input contains multiple test cases. Each test case has 3 lines. The first line is two integers n and m (1 <= n <= 10^5, 1 <= m <= 10^5), meaning the number of Tom's friends and the number of rounds of the game. The second line will be n integers ai (1 <= ai <= 10^8), meaning the numbers that Tom assigns to his friends. For every 0<=i<j<n, ai < aj, that is, the numbers are in ascending order. The third line will be m integers qi (1<=qi<=10^8), meaning the numbers that Tom will tell in turn for all the rounds of the game. The input terminates at the end of the file (EOF).

输出

For each test case, you must print a line, which includes the number sequence reported in turn by Tom's friends.

题解

  1. 二分查找,对于小明报的每个数,在有序数组中二分小于该数的最大数字

#include<bits/stdc++.h>
using namespace std;

/*
二分查找  
*/

int solve(vector<int>& nums, int t){
	int l=0, r = nums.size()-1;
	if(nums[0]>t) return nums[0];
	int mid;
	int ret;
	while(l<=r){
		mid = l+(r-l)/2;
		if(nums[mid]<t){
			l = mid+1;
			ret = nums[mid];
		}else{
			r = mid-1;
		}
	}
	return ret;
}

int main(){
	int n, m;
	while(cin >> n >> m){
		//(1)处理输入 
		vector<int> f(n);
		vector<int> t(m);
		for(int i=0; i<n; i++){
			cin >> f[i];
		}
		for(int i=0; i<m; i++){
			cin >> t[i];
		}
		
		//(2)二分查找
		for(int i=0; i<m-1; i++){
			cout << solve(f, t[i]) <<" ";
		}
		cout << solve(f, t[m-1]) << endl;	 
	}
	
}

2.分治法求最大连续子序列的和

Maximum contiguous sum

描述

Given an array A1,A2,...,An of length n, Tom wants to know the maximum continuous sum of the array, which means to find i and j for 1 <= i <= j <= n, to maximize Ai+...+Aj.

输入

The first line is an integer T, the number of test cases, followed by T test cases. Each test case contains two lines. The first line is an integer n (1<=n<=100000), representing the length of the array, and the second line includes n integers Ai (-10^18 < ∑Ai < 10^18, 1<=i<=n).

输出

For each test case, you must print the maximum continuous sum of the array.

题解

  1. dp:
    1. dp[i]:以i结尾的序列的最大和
    2. dp[i] = max(dp[i-1]+nums[i], nums[i])
    3. dp[0] = nums[0]
    4. ans:max{dp[i]}
  2. 分治
    1. 递归的将问题转化为求max(  max(左侧最大值,右侧最大值), 经过中点的连续数组的最大值)
#include<bits/stdc++.h>
using namespace std;

/*
动态规划 
*/

int main(){
	int t, n, ans;
	cin >> t;
	while(t--){
		//(1)处理输入 
		cin >> n;
		vector<int> nums(n);
		vector<int> dp(n, 0); //dp[i]表示以nums[i]结尾的连续子序列的最大和
		for(int i=0; i<n; i++){
			cin >> nums[i];
		} 
		dp[0] = nums[0];
		
		//(2)dp求解
		for(int i=1; i<n; i++){
			dp[i] = max(dp[i-1]+nums[i], nums[i]);
		}
		//(3)输出结果
		ans = dp[0];
		for(int i=1; i<n; i++){
			ans = max(ans, dp[i]);
		}
		cout << ans << endl; 
	}
	
}

#include<bits/stdc++.h>
using namespace std;
/*
1.solve1(vector<int>& nums, int l, int r)
(1)分治主函数,递归求解
(2)返回左侧、右侧、经过中间的最大值
2.solve2(vector<int>& nums, int l, int r)
(1)辅助函数
(2)求解经过中间的最大和 
*/

int solve2(vector<int>& nums, int l, int r){
	int mid = l+(r-l)/2;
	int left_sum = INT_MIN;
	int right_sum = INT_MIN;
	int sum = 0;
	for(int i=mid; i>=l; i--){
		sum+=nums[i];
		if(sum>left_sum){
			left_sum = sum;
		}
	}
	sum = 0;
	for(int i=mid+1; i<=r; i++){
		sum+=nums[i];
		if(sum>right_sum){
			right_sum = sum;
		}
	}
	return right_sum + left_sum;
}

int solve1(vector<int>& nums, int l, int r){
	if(l==r)return nums[l];
	int mid = l+(r-l)/2;
	return max(max(solve1(nums, l, mid), solve1(nums, mid+1, r)), solve2(nums, l, r) );
}

int main(){
	int t, n, ans;
	cin >> t;
	while(t--){
		//处理输入 
		cin >> n;
		vector<int> nums(n, 0);
		for(int i=0; i<n; i++){
			cin >> nums[i];
		}
		
		//分治求解
		ans = solve1(nums, 0, n-1); 
		cout << ans << endl;
	}
} 

3.在O(n)的时间复杂度内求一个序列第k小的元素

Median

描述

In RPG game, different weapons have different damages. Tom is playing Dark-Soul these days, and he wants to know the damage of the 'median' weapon, that is, half of the weapons' damages are greater than or equal to the median weapon's, and the other half's damages are less than or equal to the median weapon's. Given an odd number of weapons' damages, your task is to find the median weapon's damage.

输入

The first line of the input contains an integer t (1<=t<=5) —the number of test cases. For each test case, the first line is an integer n (1<=n<2,000,000, the number of weapons), and the second line includes n integers a1, a2, ... an (1<=ai<=10,000,000, for 1<=i<=n), and ai means the damage of weapon i.

输出

For each test case, you must print an integer that is the median weapon's damage.

题解

  1. 分治
  2. stl有库函数可以直接求一个无序序列的第k小的元素
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> nums = {3, 2, 1, 5, 4};
    int k = 3; // 求第3小的数
    
    std::nth_element(nums.begin(), nums.begin() + k - 1, nums.end());
    
    std::cout << "第" << k << "小的数是:" << nums[k - 1] << std::endl;
    
    return 0;
}
#include<bits/stdc++.h>
using namespace std;

/*
1.寻找第k小的数字 
*/

int main(){
	int t, n;
	cin >> t;
	while(t--){
		//(1)处理输入 
		cin >> n;
		vector<int> nums(n);
		for(int i=0; i<n; i++){
			cin >> nums[i];
		}
		
		//(2)寻找第k小的数字
		nth_element(nums.begin(), nums.begin()+n/2, nums.end());
		cout << nums[n/2] << endl;
	}
	
}

4.最近点对

Towers

描述

Long long ago, a hero defeated the Dark Lord, but the Dark Lord became a seed and hid himself to wait an opportunity to revive. People built magic towers to supress the power of the Dark Lord so he could not revive. However, many years later, the power of the towers become weaker and weaker. The Dark Lord revived, and he wanted to distroy all the towers to improve his power. Firstly, he decided to destroy the closest two towers. Luckly, a great sage forsaw the scenario, and the king will send soldiers to defend the two towers. The king decides to place a soldier every meter between the two towers, but he don't know which two towers are the closest and therefore how many soldiers should be sent. Could you help the king?

输入

The input contains multiple test cases. Each test case contains an integer n (1<n<10^5), the number of the towers. Then n lines follow, and each line contains two floating point numbers xi, yi, the coordinates of tower i. The input terminates at the end of the file (EOF).

输出

For each test case, you must print how many soldiers need to be placed between the closest two towers. If the distance is not an integer, round down.

 题解

  1. 分治

5. 二分答案-序列转换

Sequence transformation

描述

Given sequence A={A1,A2,...,An}, it is required to change some elements in sequence A to form a strictly monotonic sequence B (strictly monotonic is defined as: Bi<Bi+1,for 1<=i<N).

We define the cost of the transformation from sequence A to sequence B as cost (A,B)=max(|Ai−Bi|)(1≤i≤N). Please calculate the minimum cost for the transformation.

Note that each element is an integer before and after the transformation.

输入

The input contains multiple test cases. Each test case contains an integer N (0<N<10^5), the length of the sequence. The next line are N integers a1, a2, ... , an (0<ai<10^6, for 1<=i<=N). The input terminates at the end of the file (EOF).

输出

For each test case, print the minimum cost.

题解

  1. 二分,在所有可能的代价中,寻找满足条件的最小的一个
  2. 二段性=》如果mid不行,则根据条件可以判断出有一侧是不行的
#include<bits/stdc++.h>
using namespace std;

/*
二分答案
1.isOK:
能否在代价为m的情况下实现调整
nums数组最大值和最小值的差是否<=m 
2.solve:
二分答案主函数
范围:0~1e6 
*/
// 判断在给定成本x下,是否能将序列A转换为严格单调递增的序列
bool CanTransform(vector<int>& a, int x) {
    int prev = a[0] - x; // 初始化序列B的第一个元素的可能最小值
    for(int i = 1; i < a.size(); ++i) {
        int currentMin = max(prev + 1, a[i] - x); // 确保严格单调递增且尽量接近原值
        // 如果当前元素调整后与原值的差大于x,说明无法在成本x内完成转换
        if(abs(currentMin - a[i]) > x) {
            return false;
        }
        prev = currentMin; // 更新前一个元素的值为当前元素调整后的值
    }
    return true;
}

int solve(vector<int>& nums){
	int r = 1e6;
	int l = 0;
	int mid;
	while(l<r){
		mid = l+(r-l)/2;
		//(1)如果可以在成本mid下完成转换,则尝试更小的成本 
		if(CanTransform(nums, mid)){
			r = mid; 
		}else{
		//(2)否则尝试更大的成本 
			l = mid+1;
		}
	} 
	return r;
}

int main(){
	int t, n;
	cin >> t;
	while(t--){
		//(1)处理输入 
		cin >> n;
		vector<int> nums(n);
		for(int i=0; i<n; i++){
			cin >> nums[i];
		}
		//(2)二分答案 
		cout << solve(nums) << endl; 
	} 
	
}

 6.二分答案-穿越城市

A soldier's task

描述

On a map, there are n+1 cities in a row, numbered from 0 to N (1<=N<=100,000). City 0 is on the left of all cities. For city i, its position away from city 0 is Ai (1<=Ai<=10^18). A soldier wants to travel from city 0 to city N (first city 1, then city 2, ... , at last city N) in no more than K (1<=K<=100,000) days to complete his task, but he has to take a rest in some city at the end of each day. The soldier wants to know the minimum daily travel to complete the task.

输入

The input contains multiple test cases. For each test case, the first line contains two integer N(1<=N<=100,000)and K (1<=K<=100,000),

and the second line includes n integers A1, A2, ... An (1<=Ai<=10^18, Ai<Ai+1, for each i).

输出

For each test case, output an integer, which represents the minimun daily travel.

题解

  1. 注意二分答案的区间范围,数据量的大小
#include<bits/stdc++.h>
using namespace std;

/*
二分答案 
*/

bool canReach(vector<long long >& distances, long long rate, long long t){
	//(1)一天内已走距离 
	long long current = 0;
	long long  day = 0;
	//(2)遍历点 
	for(long long i=1; i<distances.size(); i++){
		//(3)如果这一天还可以继续走到下一个城市,则更新一天内已走距离 
		if(current + distances[i] <= rate){
			current+=distances[i];
		}else{
			//(4)否则休息,并更新天数,已走距离,退步 
			current = 0;
			day++;
			i--; 
		}
	}
	return day<=t; 
}


long long solve(vector<long long>& nums, long long t){
	//(1)确定二分的范围,distances[i]表示点i到点i+1的距离 
	vector<long long> distances(nums.size()+1);
	distances[1] = nums[1];
	//(2)二分的左端点是,从点i-1到点i的最大距离,因为要保证可以走过去 
	long long l = nums[1];
	for(int i=2; i<nums.size(); i++){
		distances[i] = nums[i] - nums[i-1];
		l = max(l, distances[i]);
	}
	//(3)二分的右端点是nums[n]
	long long r = nums[nums.size()-1];
	//(4)二分求解
	while(l<r){
		int mid = l+(r-l)/2;
		//(5)如果当前每日行程可以,则寻找更小的每日行程 
		if(canReach(distances, mid, t)){
			 r = mid;
		}else{
			//(6)否则尝试更大的每日行程
			l = mid+1; 
		} 
	}
	return r; 
}

int main(){
	long long n, m;
	while(cin >> n >> m){
		//(1)处理输入 
		vector<long long> f(n+1); 
		for(int i=1; i<=n; i++){
			cin >> f[i];
		}
		
		//(2)二分答案 
		cout << solve(f, m) << endl;	 
	}
	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值