美团2021校招笔试-编程题(通用编程试题,第10场)

第一题:淘汰分数

题意:某比赛已经进入了淘汰赛阶段,已知共有n名选手参与了此阶段比赛,他们的得分分别是a_1,a_2….a_n,小美作为比赛的裁判希望设定一个分数线m,使得所有分数大于m的选手晋级,其他人淘汰。

但是为了保护粉丝脆弱的心脏,小美希望晋级和淘汰的人数均在[x,y]之间。

显然这个m有可能是不存在的,也有可能存在多个m,如果不存在,请你输出-1,如果存在多个,请你输出符合条件的最低的分数线。

思路:我们先对a数组从小到大排序
首先我们讨论无解的情况:1.n-xy(很显然,仔细推导就出来了)
其次讨论有解的时候怎样最优:
1.如果sum-x>y,说明我们只能令y为及格人数,这样不及格人数才会尽可能少,m才会尽可能小,所以令及格人数为y,不及格人数为sum-y,此时m就等于a[sum-y]

2.如果sum-x<=y,那么我们直接令不及格人数为最小x,及格人数就是sum-x,
此时m就是a[x]

AC_Code:

#include
using namespace std;
const int Maxn = 5e4+10;
int a[Maxn];
int main(){
	ios::sync_with_stdio(false); 
	int n,x,y;
	cin>>n>>x>>y;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+n+1);
	int pos = 1;
	//无解
	if((n-x)<x||(n-y)>y) cout<<-1<<'\n';
	//有解
	else{
		if((n-x)>y) pos = n-y;
		else pos = x;
 	}  
 	cout<<a[pos]<<'\n';
	return 0;
} 

第二题:正则序列

题意:我们称一个长度为n的序列为正则序列,当且仅当该序列是一个由1~n组成的排列,即该序列由n个正整数组成,取值在[1,n]范围,且不存在重复的数,同时正则序列不要求排序,有一天小团得到了一个长度为n的任意序列s,他需要在有限次操作内,将这个序列变成一个正则序列,每次操作他可以任选序列中的一个数字,并将该数字加一或者减一。

请问他最少用多少次操作可以把这个序列变成正则序列?

思路:首先有n个数,要把这n个数变成正则序列,则需要1~n都出现一次,那么贪心一下,就是把序列从小到大排序,然后把第一个数变成1,第二个数变成2,…,第n个数变成n,然后算总的操作次数就行

AC_Code

#include
using namespace std;
typedef long long ll;
const int Maxn = 2e4+10;
ll a[Maxn];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	ll ans = 0;
	for(int i=1;i<=n;i++){
		ans+=abs(i-a[i]);
	}
	cout<<ans<<'\n';
	return 0;
} 

第三题:公司食堂

题意:小美和小团所在公司的食堂有N张餐桌,从左到右摆成一排,每张餐桌有2张餐椅供至多2人用餐,公司职员排队进入食堂用餐。小美发现职员用餐的一个规律并告诉小团:当男职员进入食堂时,他会优先选择已经坐有1人的餐桌用餐,只有当每张餐桌要么空着要么坐满2人时,他才会考虑空着的餐桌;

当女职员进入食堂时,她会优先选择未坐人的餐桌用餐,只有当每张餐桌都坐有至少1人时,她才会考虑已经坐有1人的餐桌;

无论男女,当有多张餐桌供职员选择时,他会选择最靠左的餐桌用餐。现在食堂内已有若干人在用餐,另外M个人正排队进入食堂,小团会根据小美告诉他的规律预测排队的每个人分别会坐哪张餐桌。

思路:这道题只需要开两个小根堆维护一下就可以了

AC_Code:

#include
using namespace std;
priority_queue<int,vector<int>,greater<int> >q0; 
priority_queue<int,vector<int>,greater<int> >q1; 
const int Maxn = 1e6+10; 
char s[Maxn];
char number[Maxn];
int ans[Maxn];
int main(){

	int t;
	scanf("%d",&t);
	while(t--){
		while(!q0.empty()) q0.pop();
		while(!q1.empty()) q1.pop();

		
		int n;
		scanf("%d",&n);
		scanf("%s",number+1);
		for(int i=1;i<=n;i++){
			if(number[i]=='0'){
				q0.push(i);
			} 
			else if(number[i]=='1'){
				q1.push(i);
			}
		}
		int m;
		scanf("%d",&m);
		scanf("%s",s+1);
		for(int i=1;i<=m;i++){
		    if(s[i]=='F'){
		    	if(!q0.empty()){
					ans[i] = q0.top();
					q0.pop();
					q1.push(ans[i]);
				}	
				else{
					ans[i] = q1.top();
					q1.pop();
				}
			}
		    else{
		    	if(!q1.empty()){
					ans[i] = q1.top();
					q1.pop();
				}
		    	else{
					ans[i] = q0.top();
					q0.pop();
					q1.push(ans[i]);
				}	
			}
		}
		for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	} 
} 

第四题:最优二叉树II

题意:小团有一个由N个节点组成的二叉树,每个节点有一个权值。定义二叉树每条边的开销为其两端节点权值的乘积,二叉树的总开销即每条边的开销之和。小团按照二叉树的中序遍历依次记录下每个节点的权值,即他记录下了N个数,第i个数表示位于中序遍历第i个位置的节点的权值。之后由于某种原因,小团遗忘了二叉树的具体结构。在所有可能的二叉树中,总开销最小的二叉树被称为最优二叉树。现在,小团请小美求出最优二叉树的总开销。

思路:这道题是一个树形dp,我们设f[l][r][root]表示以root为根节点的子树,左子树节点从 l 到 i − 1 l到i-1 li1,右子树节点从 i + 1 到 r i+1到r i+1r,于是状态转移方程就是

const int INF = 1e9
int res = INF;
for(int i=l;i<=r;i++){
int left = solve(l,i-1,i);
int right = solve(i+1,r,i);
res = min(res,left+right+a[i]*a[root]);
}
f[l][r][root] = res;

AC_Code:

#include
using namespace std;
const int Maxn = 305;
const int INF = 1e9;
int f[Maxn][Maxn][Maxn];//f[l][r][i]以第i个节点为根,左子树节点从l~i-1个,右子从i+1~r 
void init(){
    memset(f,-1,sizeof(f));
    return;
}
int a[Maxn];
int solve(int l,int r,int root){
	if(l>r) return 0;
	if(f[l][r][root]!=-1) return f[l][r][root];
	int res = INF;
	for(int i=l;i<=r;i++){
		int left = solve(l,i-1,i);
		int right = solve(i+1,r,i);
		res = min(res,left+right+a[i]*a[root]); 
	}
	f[l][r][root] = res;
	return f[l][r][root] = res;
} 
int main(){
	init();
	ios::sync_with_stdio(false);
	int n;
	cin>>n;
	a[0] = 0;
	for(int i=1;i<=n;i++) cin>>a[i];
	cout<<solve(1,n,0);
	return 0;
}

你可能感兴趣的:(大厂笔试真题,dp,算法,大厂笔试)