美团 2023年春招 技术综合-算法策略方向

捕获

时间限制:3000MS
内存限制:589824KB

题目描述

小美在玩一项游戏。该游戏的目标是尽可能抓获敌人。
敌人的位置将被一个二维坐标(x, y)所描述。
小美有一个全屏技能,该技能能一次性将若干敌人一次性捕获。
捕获的敌人之间的横坐标的最大差值不能大于A,纵坐标的最大差值不能大于B。
现在给出所有敌人的坐标,你的任务是计算小美一次性最多能使用技能捕获多少敌人。

输入描述

第一行三个整数N,A,B,表示共有N个敌人,小美的全屏技能的参数A和参数B。
接下来N行,每行两个数字x,y,描述一个敌人所在的坐标。
1≤N≤500,1≤A,B≤1000,1≤x,y≤1000。

输出描述

一行,一个整数表示小美使用技能单次所可以捕获的最多数量。

样例输入

3 1 1
1 1
1 2
1 3

样例愉出

2

思路:这道题如果直接在二维数组中标点并进行统计的话,由于图坐标范围比较大,因此不太可靠,同时点数相对二维图的大小来说过于少了。换个思路,其实只需要对每个点统计这个点作为左上、左下、右上、右下点时包含的数量即可,每次加入第i个点的时候,和前面i-1个点计算一下并维护一下各自的四个值。举例来说,如果一个点i作为左上角时包含一个点t,那么t作为右下角时一定也会包含点i,当然值得注意的是这个不能说明点i作为左上角和点t作为右下角对应的矩形是相同的,只能说各自在对方的包含域里而已。

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

struct Point{
	int x, y, left_up_num, left_down_num, right_up_num, right_down_num;
};

const int maxn = 5e2 + 5;
Point points[maxn];

int main(){
	int N, A, B, x, y, idx, pre_idx, max_num = 0, diff_x, diff_y;
	cin >> N >> A >> B;
	for(idx = 0; idx < N; ++ idx){
		cin >> x >> y;
		points[idx] = Point{x, y, 1, 1, 1, 1};
		for(pre_idx = 0; pre_idx < idx; ++ pre_idx){
			diff_x = points[idx].x - points[pre_idx].x;
			diff_y = points[idx].y - points[pre_idx].y;
			
			if(abs(diff_x) > A || abs(diff_y) > B){
				continue;
			}
			
			//由于存在共线即diff_x = 0或diff_y = 0的情况 因此不能直接else  
			if(diff_x <= 0 && diff_y <= 0){// 当前点作为左上角  对应点作为右下角 
				//cout << "(" << points[idx].x << "," << points[idx].y << ")" << "作为左上角 包含(" << points[pre_idx].x << "," << points[pre_idx].y << ")" << endl;
				++ points[idx].left_up_num;
				++ points[pre_idx].right_down_num;
				max_num = max(max_num, max(points[idx].left_up_num, points[pre_idx].right_down_num));
			}
			if(diff_x >= 0 && diff_y <= 0){// 当前点作为左下角  对应点作为右上角 
				//cout << "(" << points[idx].x << "," << points[idx].y << ")" << "作为左下角 包含(" << points[pre_idx].x << "," << points[pre_idx].y << ")" << endl;
				++ points[idx].left_down_num;
				++ points[pre_idx].right_up_num;
				max_num = max(max_num, max(points[idx].left_down_num, points[pre_idx].right_up_num));
			}
			if(diff_x <= 0 && diff_y >= 0){// 当前点作为右下角  对应点作为左下角 
				//cout << "(" << points[idx].x << "," << points[idx].y << ")" << "作为右下角 包含(" << points[pre_idx].x << "," << points[pre_idx].y << ")" << endl;
				++ points[idx].right_down_num;
				++ points[pre_idx].left_up_num;
				max_num = max(max_num, max(points[idx].right_down_num, points[pre_idx].left_up_num));
			}
			if(diff_x >= 0 && diff_y >= 0){// 当前点作为右上角  对应点作为左下角 
				//cout << "(" << points[idx].x << "," << points[idx].y << ")" << "作为右上角 包含(" << points[pre_idx].x << "," << points[pre_idx].y << ")" << endl;
				++ points[idx].right_up_num;
				++ points[pre_idx].left_down_num;
				max_num = max(max_num, max(points[idx].right_up_num, points[pre_idx].left_down_num));
			}
		}
		//cout << "左上 左下 右上 右下" << endl;
		for(pre_idx = 0; pre_idx <= idx; ++ pre_idx){
			cout << "  " << points[idx].left_up_num << "    " << points[idx].left_down_num << "    " << points[idx].right_up_num << "    " << points[idx].right_down_num << endl;
		}
	}
	cout << max_num; 
}

彩带

时间限制:3000MS

内存限制:589824KB

题目描述

小美现在有一串彩带,假定每一厘米的彩带上都是一种色彩。
因为任务的需要,小美希望从彩带上截取一段,使得彩带中的颜色数量不超过K种。
显然,这样的截取方法可能非常多。于是小美决定尽量长地截取一段。
你的任务是帮助小美截取尽量长的一段,使得这段彩带上不同的色彩数量不超过K种。

输入描述

第一行两个整数N,K,以空格分开,分别表示彩带有N厘米长,你截取的一段连续的彩带不能超过K种颜色。
接下来一行N个整数,每个整数表示一种色彩,相同的整数表示相同的色彩。1≤N,K<5000,彩带上的颜色数字介于[1,2000]之间。

输出描述

一行,一个整数,表示选取的彩带的最大长度。

样例输入

8 3
1 2 3 2 1 4 5 1

样例输出

5

思路:我们对于每一种颜色记录该颜色上一次出现的位置,初始的时候,所有颜色的上一次出现位置为0。我们每次读取一个新的颜色,并判断当前颜色是否在已包含的颜色集合里,如果不存在,则增加一个颜色数量,同时判断数量是否超过K,如果超过,则在已有颜色里找到上一次出现位置最靠前的踢出集合,并将区间左端点(彩带最左端的前一个位置)移动到该位置,同时维护答案。

#include<iostream>
#include<map>
using namespace std;
const int maxn = 5e3 + 5;

map<int, int> color_idx_map; // idx:color 因为需要删除最靠前的颜色 
map<int, int>::iterator color_idx_map_iter;
int color_pre_idx[maxn];
bool color_in_set[maxn]; 

int main(){
	int N, K, idx, left = 0, color, color_num = 0, pre_idx, interval_max_len = 0, first_color;
	cin >> N >> K;
	for(idx = 1; idx <= N; ++ idx){
		cin >> color;
		if(!color_in_set[color]){//未在集合中 
			++ color_num;
			color_pre_idx[color] = idx; 
			color_in_set[color] = true;
			color_idx_map[idx] = color;
			if(color_num > K){//如果颜色数量超过K 扔掉第一个颜色 
				left = color_idx_map.begin() -> first;
				first_color = color_idx_map.begin() -> second;
				color_idx_map.erase(color_idx_map.begin());
				color_in_set[first_color] = false;
			}
		}else{//在集合中 更新集合内部维护的左端点顺序 
			pre_idx = color_pre_idx[color];
			color_idx_map_iter = color_idx_map.find(pre_idx);
			color_idx_map.erase(color_idx_map_iter);
			color_idx_map[idx] = color;
		} 
		//经过上面的操作 现在的区间满足颜色个数不超过K个 可以直接更新答案 
		interval_max_len = max(interval_max_len, idx - left);
	}	 
	cout << interval_max_len;
	return 0;
} 

回文串

时间限制:3000MS
内存限制:589824KB

题目描述

现在小美获得了一个字符串。小羊想要使得这个字符串是回文串。
小美找到了你。你可以将字符串中至多两个位置改为任意小与英文字符'a'-'z'。
你的任务是帮助小美在当前制约下,获得字典序最小的回文字符串。
数据保证能在题目限制下形成回文字符串。
注:回文字符串:即一个字符串从前向后和从后向前是完全一致的字符串。
例如字符串ababa, aaaa,acca都是回文字符串。字符串abed, acea都不是回文字符串

输入描述

一行,一个字符串,字符串中仅由小写英文字符构成。
保证字符串不会是空字符串。
字符串长度介于[1, 100000]之间。

输出描述

一行,一个在题目条件限制下所可以获得的字典序最小的回文字符串。

样例输入

acca

样例输出

aaaa

思路:字符串,扫一遍看多少个位置不同

        A、如果没有位置不同,从头开始尝试变a,如果头已经是a,就考虑下一个,以此类推。

        B、如果只有一个位置不同,考虑前面部分的是不是a

                1、如果前面的是a,把后面的变a;现在还剩一次改动机会,需要考虑串的字符数是奇数还是偶数。如果是奇数,考虑把中心字符改为a;如果为偶数,则没法动,因为如果想把下一位改a,由于它们已经回文,要么本来就是a无需更改,要么两个相等且不为a,要同时改两个。

                2、如果前面的不是a,把前面的改为a后,若后面对应位置的也不为a,则后面的也改为a,用完两次机会。否则在为奇数串的情况下改中心字符为a。 

                总结来说:优先把不同的位置都变为a,如果有多余的机会,看是否可以改中心。

        C、如果有两个位置不同,对于每个不同的位置,考虑把字典序大的改成小的,因为如果前面的字典序大,改小更好,如果前面的字典序小,那只能选择把后面的改小。

#include<iostream>
#include<vector>
#include<string>
using namespace std;
int main(){
	string str;
	cin >> str;
	int str_len = str.length(), left = 0, right = str_len - 1, not_equal_num = 0, idx;
	vector<int> not_equal_idx;
	while(left < right){
		if(str[left] != str[right]){
			++ not_equal_num;
			not_equal_idx.push_back(left);	
			not_equal_idx.push_back(right);
		} 
		++ left;
		-- right;
	}
	if(not_equal_num == 0){//没有不同 
		left = 0, right = str_len - 1;
		while(left < right){
			if(str[left] != 'a'){
				str[left] = str[right] = 'a';
				break;
			} 
			++ left;
			-- right;
		} 
	}else if(not_equal_num == 1){//1个不同 
		left = not_equal_idx[0];
		right = not_equal_idx[1];
		str[left] = str[right] = min(str[left], str[right]);
		if(str_len & 1){
			str[str_len >> 1] = 'a';
		}
	}else{//2个不同 
		for(idx = 0; idx <= 2; idx += 2){
			left = not_equal_idx[idx];
			right = not_equal_idx[idx + 1];
			str[left] = str[right] = min(str[left], str[right]);
		}
	} 
	cout << str;
	return 0;
}

商店

时间限制:3000MS
内存限制:589824KB

题目描述

现在商店里有N个物品,每个物品有原价和折扣价。
小美想要购买商品。小美拥有X元,一共Y张折扣券。
小美需要最大化购买商品的数量,并在所购商品数量尽量多的前提下,尽量减少花费。
你的任务是帮助小美求出最优情况下的商品购买数星和花费的钱数。

编入描述

第一行三个整数,以空格分开,分别表示N,X,Y.
接下来N行,每行两个整褛数,以空格分开,表示一个的原价和折扣价。
1≤Ns100,1≤X≤5000,1≤Y≤50,每个商品原价和折扣价均介于[1,50]之间。

输出描述

一行,两个整数,以空格分开。第一个数字表示最多买几个商品,第二个数字表示在满足商品尽的前提下所花费的最少的钱数。

样例输入1

3 5 1
4 3
3 1
6 5

样例输出1

2 5

 样例输入2

3 5 1
4 3
3 1
6 1

样例输出2

2 4

 样例输入3

10 30 3
2 1
3 2
2 1
10 8
6 5
4 3
2 1
10 9
5 4
4 2

样例输出3

8 24

思路:本题给的物品价格并不大,应该是dp没跑了。设dp[idx][choiced_num][discount_num]表示 在已考虑idx件物品物品、已选定choiced_num件物品、已使用discount_num个折扣的情况下的最少花费,按照如下进行分析:

        A.discount_num=0,表示当前未使用过折扣,那么前面也不能使用折扣,同时考虑到当前这件物品是否被购买(如有购买,则必须全额),有如下转移方程

\tiny dp[idx][choiced\_num][0] = min(dp[idx - 1][choiced\_num][0], dp[idx - 1][choiced\_num - 1][0]+ ori\_price[idx]);

         B.discount_num!=0,表示当前有使用过折扣,考虑当前物品的买与不买

                1.当前物品不买时,为如下状态

\tiny dp[idx-1][choiced\_num][discount\_num]

                2.当前物品购买时,分为两种,一种是前面用了discount_num-1次折扣,本次使用折扣;一种是前面用了discount_num次折扣,本次只能全额

\tiny min(dp[idx - 1][choiced\_num - 1][discount\_num]+ ori\_price[idx], dp[idx - 1][choiced\_num - 1][discount\_num - 1]+ discount\_price[idx])

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

const int maxn = 1e2 + 5;
const int maxx = 5e3 + 5;
int ori_price[maxn], discount_price[maxn], dp[maxn][maxn][55]; //i:已考虑的物品数 j:已选定物品数量 k:已使用折扣数量 dp[i][j][k]:约束下的最少花费 

int main(){
	int N, X, Y, idx, choiced_num, discount_num, max_buy_num = 0, min_buy_cost;
	cin >> N >> X >> Y;
	memset(dp, 0x3f, sizeof(dp));
	dp[0][0][0] = 0;
	min_buy_cost = X;
	for(idx = 1; idx <= N; ++ idx){
		cin >> ori_price[idx] >> discount_price[idx];
		dp[idx][0][0] = 0;//考虑idx件物品,一件都没选,也没用折扣 
		for(choiced_num = 1; choiced_num <= idx; ++ choiced_num){
			for(discount_num = 0; discount_num <= min(Y, choiced_num); ++ discount_num){
				if(discount_num == 0){//如果当前状态是未使用折扣折扣,那么之前也必须没用过,并且 要么本次没买,要么本次是全额购买 
					dp[idx][choiced_num][0] = min(dp[idx - 1][choiced_num][0], dp[idx - 1][choiced_num - 1][0]+ ori_price[idx]);
					//cout << "idx:" << idx << " choiced_num:" << choiced_num << " dis_num:" << 0 << endl;  
					//cout << "要么本次没买:" << dp[idx - 1][choiced_num][0] << " 本次是全额购买:" << dp[idx - 1][choiced_num - 1][0]+ ori_price[idx] << endl;
				} else{//如果当前状态是使用折扣 那么要么本次没用折扣,之前已经用了discount_num次,要么之前用了discount_num-1次,本次原价购买  
					dp[idx][choiced_num][discount_num] = min(dp[idx - 1][choiced_num - 1][discount_num]+ ori_price[idx], dp[idx - 1][choiced_num - 1][discount_num - 1]+ discount_price[idx]);
					//
					dp[idx][choiced_num][discount_num] = min(dp[idx][choiced_num][discount_num], dp[idx - 1][choiced_num][discount_num]);
				}
				
				if(dp[idx][choiced_num][discount_num] <= X && max_buy_num <= choiced_num){//想要花的少又买的东西多 
					if(max_buy_num == choiced_num){//如果买的数量一样 取花费少的 
						min_buy_cost = min(min_buy_cost, dp[idx][choiced_num][discount_num]);
					} else if(max_buy_num < choiced_num){//如果买的数量多 直接换到数量多的 
						min_buy_cost = dp[idx][choiced_num][discount_num];
						max_buy_num = choiced_num;
					}
				}
				//cout << "考虑的物品数:" << idx << " 已选定的数量:" << choiced_num << " 已使用折扣数量:" << discount_num << " 最少花费:" << dp[idx][choiced_num][discount_num] << endl; 
			}
		}
	}
	cout << max_buy_num << " " << (max_buy_num == 0 ? 0 : min_buy_cost); //如果一件都买不起 花费也肯定是0 
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/qq_39304630/article/details/129652584