【编程笔试】美团2021校招笔试-通用编程题第2场(附思路及C++代码)

练习地址

点此前往练习

小团的配送团队

小团是美团外卖的区域配送负责人,众所周知,外卖小哥一般都会同时配送若干单,小团在接单时希望把同一个小区的单子放在一起,然后由一名骑手统一配送。但是由于订单是叠在一起的,所以,他归类订单时只能知道新订单和已有的某个订单的小区是相同的,他觉得这样太麻烦了,所以希望你帮他写一个程序解决这个问题。

即给出若干个形如a b的关系,表示a号订单和b号订单是同一个小区的 ,请你把同一个小区的订单按照编号顺序排序,并分行输出,优先输出最小的订单编号较小的小区订单集合。订单的编号是1到n。(可能存在同时出现a b和b a这样的关系,也有可能出现a a这样的关系)

输入描述:

输入第一行是两个正整数n,m,表示接受的订单数量和已知的关系数量。(1<=n,m<=10000)接下来有m行,每行两个正整数a和b,表示a号订单和b号订单属于同一个小区(1<=a,b<=n);

输出描述:

输出第一行包含一个整数x,表示这些订单共来自x个不同的小区。接下来的输出包含x行,每行表示输出若干个订单编号,表示这些订单属于同一个小区,按照订单编号升序输出。优先输出最小的订单编号较小的小区。

输入例子1:

5 5

1 2

2 2

3 1

4 2

5 4

输出例子1:

1

1 2 3 4 5

思路:

根据题意,考虑使用并查集。

注意输出需要输出集合的个数,对所有的订单遍历,查找属于同一个集合中的元素,放入vector容器中。

代码:

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

int pa[N];
vector<int> g[N];
//并查集的初始化
void init(int n)
{
	for(int i = 0;i<=n;i++)
	{
		pa[i]=i;
	}
}
//并查集的查找:在x所在的集合中查找集合编号 
int find(int x) 
{
	if(x!=pa[x])
		pa[x]=x=find(pa[x]);
	return pa[x];
} 
//并查集的合并:将x和y所在的集合合并
void merge(int x,int y)
{
	x = find(x);
	y = find(y);
	if(x!=y)
	{
		if(x<y)
			pa[y]=x;
		else
			pa[x]=y;
	}
}
 
int main()
{
	int n,m,a,b;
	cin>>n>>m;
	init(n);
	for(int i=0;i<m;i++)
	{
		cin>>a>>b;
		merge(a,b);
	}
	int count = 0;
	for(int i = 1;i<=n;i++)
	{
		if(pa[i]==i)
			count++;
		int pi=find(i);
		g[pi].push_back(i);
	}
	cout<<count<<endl;
	for(int i=1;i<=n;i++)
	{
		if(pa[i]==i)
		{
			for(auto x:g[i])
				cout<<x<<" ";
			cout<<endl;
		}
	}
	return 0;
}

不一样的逆序数

小团最近对逆序数(将一个数字逐位逆序,例如1234的逆序数为4321,1100的逆序数为11)特别感兴趣,但是又觉得普通的逆序数问题有点太乏味了。

于是他想出了一个新的定义:如果一个数的4倍恰好是它的逆序数,那么称这两个数是新定义下的逆序对。

接下来给定一正整数n,问:不超过n的正整数中有多少对新定义下的逆序对?

输入描述:

单组输入。输入一个正整数n,n<1e7

输出描述:

第一行输出在不超过n的前提下有多少对逆序数,接下来每一行输出一对逆序数,以空格分隔。如果有多组逆序数,按照第一个数升序输出。如果没有一对逆序数则直接输出0即可。

输入例子1:

10000

输出例子1:

1
2178 8712

思路:

使用穷举法遍历查找满足要求的逆序数。

输出通过pair定义一个数据对,当找到满足要求的逆序数对后,将它们存放在vector容器中,计数+1。

代码:

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

typedef pair<int,int> Reverse;
//获取逆序数 
int get(int num)
{
	int reverse=0;
	while(num)
	{
		reverse = reverse*10+num%10;
		num/=10;
	} 
	return reverse;
}

int main()
{
	int n;
	cin>>n;
	vector<Reverse> nums;
	int count=0;
	for(int i=1;i<=n;i++)
	{
		int reverse = get(i);
		if(reverse<=n&&reverse==i*4)
		{
			nums.push_back({i,reverse});
			count++;
		}
	}
	cout<<count<<endl;
	for(auto x :nums)
		cout<<x.first<<" "<<x.second<<endl;
	return 0;
}

小团的旅行路线

小团是一个旅游爱好者,快要过春节了,他想统计一下,在过去的一年中他进行过几次旅行,于是他打开了美团app的订单记录,记录显示了他的购买车票的记录。记录是按时间顺序给出的,已知一次旅行的线路一定是一个闭环,即起点和终点是同一个地点。因此当每找到一段闭合的行程,即认为完成了一次旅行。数据保证不会出现不在闭环路径中的数据。

请你在小团的购票记录中统计出他全年共进行了多少次旅行?

输入描述:

输入第一行包含一个正整数n,表示小团的购票记录数量。(1<=n<=10000)接下来有n行,每行是两个长度不超过10的仅由小写字母组成的字符串S_a S_b,表示购买了一张从S_a到S_b的车票。

输出描述:

输出仅包含一个整数,表示小团的旅行次数。

输入例子1:

6

beijing nanjing

nanjing guangzhou

guangzhou shanghai

shanghai beijing

fuzhou beijing

beijing fuzhou

输出例子1:

2

思路:

利用题设:起点和终点是同一个地点。当满足该条件时,计数+1。

代码:

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

int main()
{
	int n;
	cin>>n;
	string a,b,start="";
	int count=0;
	for(int i=1;i<=n;i++)
	{
		cin>>a>>b;
		if(start=="")
		{
			start=a;
		}
		if(b==start)
		{
			count++;
			start="";
		}
	}
	cout<<count<<endl; 
	return 0;
}

小团的车辆调度

小团是美团汽车租赁公司的调度师,某个时刻A和B两地都向该公司提交了租车的订单,分别需要a和b辆汽车。此时,公司的所有车辆都在外运营,通过北斗定位,可以得到所有车辆的位置,小团分别计算了每辆车前往A地和B地完成订单的利润。作为一名精明的调度师,当然是想让公司的利润最大化了。

请你帮他分别选择a辆车完成A地的任务,选择b辆车完成B地的任务。使得公司获利最大,每辆车最多只能完成一地的任务。
输入描述:

输入第一行包含三个整数n,a,b,分别表示公司的车辆数量和A,B两地订单所需数量,保证a+b<=n。(1<=n<=2000)接下来有n行,每行两个正整数x,y,分别表示该车完成A地任务的利润和B地任务的利润。

输出描述:

输出仅包含一个正整数,表示公司最大获得的利润和。

输入例子1:

5 2 2

4 2

3 3

5 4

5 3

1 5

输出例子1:

18

思路:

根据题意,可以抽象成0/1背包问题,考虑使用滚动数组优化的动态规划法。

设dp[i][j][k]表示第i辆车,则可以得到如下转移式:

d p [ i ] [ j ] [ k ] = m a x { d p [ i − 1 ] [ j − 1 ] [ k ] + A [ i ] , d p [ i − 1 ] [ j ] [ k − 1 ] + B [ i ] , d p [ i − 1 ] [ j ] [ k ] } dp[i][j][k]=max\{dp[i-1][j-1][k]+A[i],dp[i-1][j][k-1]+B[i],dp[i-1][j][k]\} dp[i][j][k]=max{dp[i1][j1][k]+A[i],dp[i1][j][k1]+B[i],dp[i1][j][k]}

同时考虑合法状态,对于每一个合法状态dp[i][j][k],必定会有i≥j+k。所以在转移前需要考虑是否能够转移。

下述代码只过了4/10组用例,AC代码详见评论区

代码:

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

int dp[2][N][N];
int A[N],B[N];
int main()
{
	int n,a,b;
	cin>>n>>a>>b;
	for(int i=1;i<=n;i++)
	{
		cin>>A[i]>>B[i];
	}
	for(int i=1;i<=n;i++)
	{
		dp[i%2][1][0]=A[i];
		dp[i%2][0][1]=B[i];
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k+j<=i;k++)
			{
				dp[i%2][j][k]=max(dp[(i-1)%2][j-1][k]+A[i],dp[(i-1)%2][j][k-1]+B[i]);
				if(i-1>=j+k)
					dp[i%2][j][k]=max(dp[i%2][j][k],dp[(i-1)%2][j][k]);
			}
		}
	}
	cout<<dp[n%2][a][b];
	return 0;
}

总结

第一题:

  1. 并查集模板:
#define N 10010

int pa[N];
//并查集的初始化
void init(int n)
{
	for(int i = 0;i<=n;i++)
	{
		pa[i]=i;
	}
}
//并查集的查找:在x所在的集合中查找集合编号 
int find(int x) 
{
	if(x!=pa[x])
		pa[x]=x=find(pa[x]);
	return pa[x];
} 
//并查集的合并:将x和y所在的集合合并
void merge(int x,int y)
{
	x = find(x);
	y = find(y);
	if(x!=y)
	{
		if(x<y)
			pa[y]=x;
		else
			pa[x]=y;
	}
}
  1. for(auto i : v)遍历容器元素:C++11的新特性。
    1. v是一个可遍历的容器或流,比如vector类型,i就用来在遍历过程中获得容器里的每一个元素。
    2. 等价于for(some_iterator it = v.begin(); it != v.end(); ++it)some_type i = *it
    3. 另外还可以是for(auto**&** i : v),for(auto**&&** i: v),区别在于i是值还是引用。

第二题:

  1. 获取一个数的逆序数:
//获取逆序数 
int get(int num)
{
	int reverse=0;
	while(num)
	{
		reverse = reverse*10+num%10;
		num/=10;
	} 
	return reverse;
}
  1. 输出是数据对时,可通过pair定义一个数据对,再使用vec.push_back(Pair)将其存放入vector容器中。

    • pair:将2个数据组合成一组数据。

      • 头文件#include <utility>
      • 创建空的pair对象:pair<T1, T2> p;
      • 以v1和v2的值创建pair对象:make_pair(v1, v2);
      • pair对象的比较运算:p1 op p2;op是比较运算符>或<,遵循字典次序。
      • pair对象的相等:p1 == p2;p1和p2的first和second依次相等。
      • 返回pair对象中第一个公有数据成员:p.first;
      • 返回pair对象中第二个公有数据成员:p.second;
      • 在某些清况函数会以pair对象作为返回值时,可以直接通过std::tie进行接收。
    • C++ vector中使用pair:

      • 声明vector:vector< pair<T1,T2> > vec;注意:vector<> 与里面的pair<T1,T2>得有间隔,否则会报错,编译器会识别成>>运算符的重载。

      • 向vector中插入pair数据:vec.push_back(make_pair<T1,T2>(a,b));

      • 定义迭代器:

        vector<pair<int,int> > ::iterator iter;
        for(iter=vec.begin();iter!=vec.end();iter++);
        
      • 数据读取:
        第一个数据:(*iter).first
        第二个数据:(*iter).second

第三题:

暂无

第四题:

  1. 动态规划(DP)解决0/1背包问题:

    • 背包容量不足以装入物品i,则装入前i个物品得到的最大价值和装入前i-1个物品得到的最大价值是相同的;
    • 背包容量可以装入物品i,如果把第i个物品装入背包,则背包中物品的价值等于把前i-1个物品装入容量为j-wi的背包中的价值加上第i个物品的价值vi;如果第i个物品没有装入背包,则背包中物品的价值等于把前i-1个物品装入容量为j的背包中所取得的价值。取二者中的较大者作为把前i个物品装入容量为j的背包中的最优解。
    • 递推式:

V ( i , j ) = { V ( i − 1 , j ) j < w i m a x { V ( i − 1 , j ) , V ( i − 1 , j − w i ) + v i } j ≥ w i V(i,j)=\left\{ \begin{array}{lr} V(i-1,j)&j<w_i\\ max\{V(i-1,j),V(i-1,j-w_i)+v_i\}&j≥w_i \end{array} \right. V(i,j)={V(i1,j)max{V(i1,j),V(i1,jwi)+vi}j<wijwi

  1. 滚动数组优化动态规划的空间

    二维数组例子:

    int i, j, d[100][100];
    for(i = 1; i < 100; i++)
        for(j = 0; j < 100; j++)
            d[i][j] = d[i - 1][j] + d[i][j - 1];
    

    由于d[i][j]只依赖于d[i - 1][j], d[i][j - 1],因此使用滚动数组实现如下:

    int i, j, d[2][100];
    for(i = 1; i < 100; i++)
        for(j = 0; j < 100; j++)
            d[i % 2][j] = d[(i - 1) % 2][j] + d[i % 2][j - 1];
    

    此处%2也可以用&1。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值