<题单>#2题解

可以参考其他题解:

tql:https://blog.csdn.net/IST20008/article/details/114604922

数组-3

删除数组中重复出现的数字,多个重复的数字仅保留第一个出现的。

题解

用set去存储数字,用count()判断是否出现过即可,最后答案存储到我们的vector之中,用增强for循环进行迭代。

        

#include<bits/stdc++.h>
using namespace std;
int main()
{
	set<int>s;
	int n;
	cin >> n;
	vector<int>v;
	for (int i = 1; i <= n; i++)
	{
		int x;
		cin >> x;
		if (!s.count(x))
		{
			v.push_back(x);
			s.insert(x);
		}
	}
	int f = 1;
	for (int i : v)
	{
		if (f)
		{
			cout << i;
			f = 0;
		}
		else
		{
			cout <<" "<< i ;
		}
	}
	cout << endl;
}

四个数的总和为0

有4个集合A、B、C、D,每个集合都是由m个整数组成。(m<=2000)

要求分别从四个集合中挑出一个元素a、b、c、d,使得a+b+c+d=0。(同一集合中每个元素的值不大于2的28次方)

问有多少种不同的选法?

方法一:(map)

首先如果我们依次去循环遍历a,b,c,d,共四个集合,时间复杂度为\(O(n^4)\)很显然会超时。

因此我们可以花费\(O(n^2)\)将a的每一个元素+b的每一个元素后的结果存储到我们的map里面,假设\(a+b=x\) ,存储到map,mp[a+b]++

然后我们再花费\(O(n^2)\)去遍历c和d里的每一个元素并且两两相加,即\(c_i+d_i=y\),累加mp[-y]即可。

map存数也需要花费一定的时间,平均时间复杂度\(O(log_2n)\)

时间复杂度:\(O(n^2)\)

空间复杂度:最坏的情况下\(O(n^2)\)

#include<iostream>
#include<set>
#include<unordered_set>
#include<map>
using namespace std;
int main()
{
	multiset <long long>a, b, c, d;
	int m;
	cin >> m;
	long long x;
	for (int i = 1; i <= m; i++)
	{
		cin >> x;
		a.insert(x);
	}
	for (int i = 1; i <= m; i++)
	{
		cin >> x;
		b.insert(x);
	}
	for (int i = 1; i <= m; i++)
	{
		cin >> x;
		c.insert(x);
	}
	for (int i = 1; i <= m; i++)
	{
		cin >> x;
		d.insert(x);
	}
	long long cnt = 0;
	multiset<long long >left;
	multiset<long long, greater<long long>>right;
	map<int,int>mp;
	
	for (auto it_a : a)
	{
		for (auto it_b : b)
		{
			mp[it_a + it_b]++;
		}
	}
	for (auto it_c : c)
	{
		for (auto it_d : d)
		{
			cnt+=mp[-(it_c + it_d)];
		}
	

方法二:离散化双指针处理

  1. 首先对a,b集合进行相加处理获得a+b集合命名为ab,并且以从大到小排序。
  2. 对c,d集合进行相加处理,并且获得c+d集合命名为cd,并且以小到大排序。
  3. 设定指向ab下标的指针p,设指向cd的指针q,并且默认初始为0.
  4. 我们的最终结果为 \(ans=ab[p]+cd[q]\)
    • \(ans<0\),q右移使得\(cd[p]\)增大。
    • \(ans>0\),p左移使得\(ab[p]\)变小。
    • \(ans=0\),这时候不能轻易移动指针,因为可能存在多个重复的数据,因此需要再来两个变量去模拟后续相同的结果,详情看代码 。


平衡膳食

我们假设一个人吃一顿饭至少要摄入三种不同类型的维生素才能满足营养需求。

现在这里有含维生素 A 的食物 a 份,含维生素 B 的食物 b 份,含维生素 C 的食物 c 份和含维生素 D 的食物 d 份,请问这些食物最多可以满足多少人的营养需求。

解析

贪心思想,每次都拿最多的三个,即不拿最小的。

证明:是否为局部最优 && 是否能推出全局最优

假设我们拿了最小的,就会造成最小的为0,多的反而被浪费,因此局部最优,如果拿最小的会导致后续多余的无法被匹配和消去,造成浪费,因此我们一直按这种策略进行可以推出全局最优。

用数组不断进行排序即可。

#include<bits/stdc++.h> 
using namespace std;
int main()
{
	int a, b, c, d;
	int n;
	cin >> n;
	int r[4];
	while (n--)
	{
		cin >> r[0] >> r[1] >> r[2] >> r[3];
		int cnt = 0;
		sort(r, r + 4);
		while (r[1]!=0)
		{
			r[1]--;
			r[2]--;
			r[3]--;
			cnt++;
			sort(r, r + 4);
		}
		cout << cnt << endl;
	}
}			

平衡膳食-2

我们保持 平衡膳食 这道题目的假设,也就是一个人一顿饭至少需要摄入三种不同类型的维生素才能满足营养需求。现在有 n 个食品,每个食品都有一个编号a,表示这个食品含有维生素a,请问这些食品最多可以使多少人达到营养需求。

解析

我们发现现在需要对编号进行统计,因此我们可以用map进行处理,最后将每个食品的数量存入到multiset里面,不断取最大的三个即可。

        
#include<bits/stdc++.h> 
using namespace std;
int main()
{
	int n;
	cin >> n;
	map<int, int>mp;//这个按键排序 要让他变成值排序用set去维护
	multiset<int, greater<int>>s;//降序
	while (n--)
	{
		int x;
		cin >> x;
		mp[x]++;
	}
	for (auto it : mp)
	{
			s.insert(it.second);
	}
	long long cnt = 0;
	while (s.size()>2)//最少要3个
	{

		auto it = s.begin();
		auto a = it++;
		auto b = it++;
		auto c = it;
		if (*a > 1)
		{
			s.insert(*a - 1);
			s.erase(a);
		}
		else
		{
			s.erase(a);
		}
		if (*b > 1)
		{
			s.insert(*b - 1);
			s.erase(b);
		}
		else
		{
			s.erase(b);
		}
		if (*c > 1)
		{
			s.insert(*c - 1);
			s.erase(c);
		}
		else
		{
			s.erase(c);
		}
		cnt++;
	}
	//不断去减少后面3个
	cout << cnt<<endl;
}			

    

Kingdom Rush

所有士兵中选出战斗力大于a的最弱的士兵,如果找不到任何一个士兵战斗力大于a,那么国王就会派出战斗力最强的三个士兵去迎战(三英战吕布)。如果采用了1对1的作战方式,那么在作战完毕后,那个士兵的战斗力会下降a点;如果采用了3对1的作战方式,那么这三个士兵就会在战斗中牺牲。你的任务是,在这m天战斗结束以后,帮国王统计存活士兵的信息

解析

纯模拟题目,没什么技巧,我们明确一下要使用的STL容器和STL算法

  1. 存储士兵兵力,并且需要排序--> multiset
  2. 最后需要统计各个兵力对应的数目,根据战斗力排序-->map<int,int>(map会根据键值进行排序)
  3. 找大于等于吕布战斗力的人,upper_bound()。
        
#include<bits/stdc++.h> 
using namespace std;
int main()
{
	multiset<int>s;
	int n, m;
	cin >> n >> m;
	while (n--)
	{
		int x;
		cin >> x;
		s.insert(x);
	}
	while (m--)
	{
		int x;
		cin >> x;
		auto it = s.upper_bound(x);
		if (it != s.end())//找的到
		{
			s.insert(*it - x);//先插入 再删除
			s.erase(it);
		}
		else//3v1
		{
			auto p = s.end();
			p--;
			s.erase(p--);
			s.erase(p--);
			s.erase(p);
		}
	}
	map<int, int>mp;
	for (auto it : s)
	{
		mp[it]++;
	}
	for (auto it : mp)
	{
		cout << it.first << " " << it.second << endl;
	}
}			

    

下馆子-2

前些日子,信息学院组织的下馆子活动又一次开始,罗少想知道谁是这个活动的签到达人,于是他从后台按时间顺序获取了所有人的签到信息,请你帮罗少找到签到次数最多的那个人,如果存在多个,输出先出现的那个。

解析一

对于签到信息和出现次数,我们可以用map进行一个映射,实现签到信息和出现次数的关联,即出现次数value \(value=map[string]\)

但是问题在于,当出现次数相同多的最大几个,我们优先输出最先出现的那个,因此我们还需要记录对应的人最早出现的时间。

        

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n;
	cin >> n;
	map<string, int >mp;
	map<string, int>mp2;//次数
	string ans;
	int cs = 0;
	for (int i = 1; i <= n; i++)
	{
		string s;
		cin >> s;
		mp[s]++;
		if (mp2.find(s) != mp2.end())//没有出现过 
		{
			mp2[s] = i;//最早出现的次数
		}
	}
	//记录mp2 数字最小的
	int m = 99999999;
	int list = 99999999;//最早出现的
	for (auto it : mp)
	{
		if (it.second >cs )
		{
			list = mp2[it.first];
			cs = it.second;
			ans = it.first;
		}
		else if (it.second == cs)
		{
			//比较出现的早
			if (mp2[it.first] < list)
			{
				list = mp2[it.first];
				cs = it.second;
				ans = it.first;
			}
		}
	}
	cout << ans;
}

解法二:嵌套pair

创建map的时候,T模板类可以代表任意的数据类型,因此我们可以嵌套一个pair进去,

定义为:pair<int,int>

pair->first 代表出现的次数

pair->second 代表第一次出现的序号

嵌套进map,即

map<string,pair<int,int>>    
#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n;
	cin >> n;
	map<string,pair<int,int>>mp;
	string ans="";
	int mx = -1;
	int index = 999999;//最小下标
	for (int i = 1; i <= n; i++) {
		string s;
		cin >> s;
		if (mp.find(s) == mp.end()) {//第一次
			mp[s].second = i;
		}
		mp[s].first++;
	}
	for (auto i : mp) {
		if (i.second.first > mx) {
			mx = i.second.first;//找最大值
			ans = i.first;
			index = i.second.second;
		}
		else if (i.second.first == mx) {
			if (i.second.second < index) {
				ans = i.first;
				index = i.second.second;
			}
		}
	}
	cout << ans;
}

签到题-5

嘉庚学院开展了为期两周的线上教学活动,其中出勤是一个很重要的指标。

每次有 n 个同学应该来参加线上课程,他们都有自己独特的学号来进行签到。

但总有一个同学睡过了头也就是他没有签到,要接受爆照的处罚(奖励),你能帮老师找出是谁这么幸运么?

解法一:算数性质

对于第一组数据全部相加,减去第二组数据,剩下的即是没有签到的同学。

根据异或性质,自身异或的值为0,并且0和其他数字异或还是0,可以对第一组数据进行异或,然后再将值对第二组数据进行异或,最后的值就是没有签到的同学。

解法二:set记录

将第一组数据存放到set里面,然后根据第二组数据提供的值,将对应的值从set里面删除,剩下的一个就是最后的数字

代码略

不同的和

解析

  1. dfs搜索每一种不同的情况
  2. 用last参数去维护搜索,可以使得数字不会重复,打个比方,现在有数字1,2,3,4,你每次只能按顺序递增的取数字,比如:(1 2) , (1 2 3) ,(1 2 3 4),(1 3)(1,3,4) ... 这样可以保证在每个深度不会重复搜索
        
#include<bits/stdc++.h>
using namespace  std;
typedef long long ll;
int r[21];
int n;
set<int>s;
void dfs(int step,int last,int sum ) {
	s.insert(sum);
	if (step == n) {
		return;
	}
	for (int i = last; i < n; i++) {
		dfs(step + 1, i + 1, sum + r[i]);
	}
}
int main() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> r[i];
	}
	dfs(0, 0, 0);
	cout << s.size() << endl;
}

    
posted @ 2021-03-31 15:46  Fanxuwei  阅读(279)  评论(0编辑  收藏  举报