算法沉淀第十二天(牛客练习赛146)

目录

引言:

合格的机器

        题意分析

        逻辑梳理

        代码实现

小灰灰的火焰星球2

        题意分析

        逻辑梳理

        代码实现

盲目自大的小灰灰

        题意分析

        逻辑梳理

        ·代码实现

结语:


引言:

        时隔多日,我又来沉淀算法了,今天我们来讲今晚的牛客练习赛的题目,拼尽全力,开了三题,燃尽了,如图

        为什么许久没更新了呢,因为前些天在深度梳理C语言指针方面的知识,我打算之后C语言指针部分就直接用一篇博客来写了,所以在指针博客出炉前,会有一段的沉淀期

        那么,闲话少叙,接下来我们 进入正式的算法讲解——————>

        这场比赛的链接在这(8条未读私信) 牛客练习赛146_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ

                ​​​​​​​        ​​​​​​​        ​​​​​​​        


合格的机器

        按照惯例,我们先来看题目

        题意分析

        这个题目的意思就是给你n个机子,每个机子上都有代币,如果机子上的代币为偶数个,那么这个机子就是合格的,如果机子上的代币为奇数个,那么这个机子就是不合格的

        然后,我们可以对机子进行任意次操作,每次操作都可以选择一个代币数量不低于2个的机子,从这个机子上取出一个代币,随后选择一个机子,将这个代币放到选择的机子上(也可以是本机)

        然后问你进行任意次操作后,最多能让多少台机子处于合格状态,输出台数就可以了


        逻辑梳理

        首先,因为我们是从一堆机子里选一个机子取出代币,然后再选一个机子放入代币,所以所有机子的总代币数量是不变的

        那么,我们可以把机子先进行操作,变化为每个机子的代币个数为2,然后剩余的所有代币都放到一个机子上,此时,便是合格状态最多的情况的机子了

        那么,为什么可以这么变化呢,我们来看图,如下图

        那么,此时,我们只需要判断k是奇数还是偶数就可以了,因为其余位置的机子都是合格的

        通过图的分析,大部分情况就已经讨论完了,但有一处需要注意,那便是,总的代币数量如果没有2×n个,就无法满足n-1个元素都是2个代币,所以,我们还需要进行分类讨论

        如果代币数量没有2×n个,就直接输出代币的个数取模n就可以了

        如果代币数量不少于2×n个,就在n-1个机子的情况下,再判断k代币的机子是否合规就可以了

        那么,逻辑梳理完了,接下来就进入代码实现环节


        代码实现

        这里就直接放AC码啦

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
using namespace std;

int a[200010];
void solve()
{
	int n;
	cin >> n;
	int ans = 0;
	int sum = 0;

	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		sum += a[i];
	}
	if (sum / n >= 2)
	{
		ans = (sum % 2 == 0) ? n : n - 1;
		cout << ans << endl;
	}
	else
	{
		cout << sum % n << endl;
	}
	return;
}

int main()
{
	solve();
	return 0;
}

小灰灰的火焰星球2

        题意分析

        这题就是给你一个数组,下标越靠前,温度越高,也就是这个数组下标从小到大对应的温度是单调不递增的,然后这个数组每一个位置都会有一个价值,那么初始的数组就搭建完成了

        接下来,就是给你T次施法机会,每次施法会给一个法力值,施法后可以选择俩个温度低于法力值的区域采集那俩快区域的价值,每次施法采集完俩次后,数组的区域价值就又会重置(即之后施法也可以采集相同区域的价值)

        然后,题目问你施法T次后,所能获得的最大价值为多少


        逻辑梳理

        首先,因为施法顺序的改变是不会影响最后的结果的,所以,我们可以先将T次的施法机会输入进去后,先进行排序,排序成从小到大的样子,这样就可以大幅降低之后操作的时间复杂度

        然后题目给我们的数组的温度是从高到低的,这样就要用到后缀和,但是这样操作就很麻烦,我是这么感觉的,所以可以在输入时候倒着输入(即下标从后往前),这样输入完这个数组的 温度就是单调不递减的了

        此时,因为施法顺序已经排完序了,是从小到大的,所以下一次的施法,获得的价值是肯定不低于上一次施法获得的价值的,所以,我们只需要一次循环就可以将每次施法可以获得的最多价值给算出来,这也是为什么要对施法顺序进行排序

        而具体获得多少价值,就可以通过对原数组的温度和价值进行预处理

        首先,我们可以先设俩个变量进行更新迭代,假设分别为max1和max2,,一开始将这俩个变量置为0,随后循环从1到n时,每次到下一个下标时,将当前下标的价值与max1进行比较,如果比max1大,且比max2小,就将这个价值替换为max1,如果比max1大,也比max2大,就讲max2赋值给max1,随后max2赋为当前下标的价值

        那么,我们再创一个装价值的数组b,每次max1和max2更新前或者更新后,当前下标的值也进行更改,当前下标所对应的值赋更新前和更新后的意义是不同的,那么,我们来讲一讲俩种情况下的意义,这俩种方式我都尝试了一下,在代码实现部分我会将这俩种更新方式的AC码都放出来

        第一种是在max1和max2更新前对下标的值进行更改,此时,b数组的每个下标上的元素代表的意义是:法力值若低于这个下标的温度,且比前面下标的温度高,获得的最大价值就是这个元素

        第二种是在max1和max2更新后对下标的值进行更改,此时,b数组的每个下标上的元素代表的意义是:法力值若高于这个下标的温度,且比后面下标的温度低,获得的最大价值就是这个元素 

        我个人是觉得第一种方式更清晰明了点,需要处理的步骤也少点

        当然,这俩种情况都要进行细节处理

        这两种情况下,主体代码基本大差不差,主要是要考虑极端情况,比如如果法力值比最低温度都要小,又或者法力值比最高温度还要高

        将极端情况也考虑进去后,这题就没有问题了

        那么逻辑梳理完了,接下来来看代码实现


        代码实现

        第一种AC码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
using namespace std;

int hot[100010];
int siz[100010];
int ma[100010];
int a[100010];
void solve()
{
	int n, t, max1 = 0, max2 = 0;
	cin >> n >> t;
	for (int i = n; i >= 1; i--)
	{
		cin >> hot[i];
	}
	for (int i = n; i >= 1; i--)
	{
		cin >> siz[i];
	}
	for (int i = 1; i <= n; i++)
	{
		ma[i] = max1 + max2;
		if (siz[i] > max1)
		{
			if (siz[i] > max2)
			{
				max1 = max2;
				max2 = siz[i];
			}
			else
			{
				max1 = siz[i];
			}
		}
	}
	hot[n + 1] = 1000000;
	ma[n + 1] = max1+max2;
	for(int i = 1;i<=t;i++)
	{
		cin >> a[i];
	}
	sort(a + 1, a + t + 1);
	int left = 1;
	long long ans = 0;
	for (int i = 1; i <= t; i++)
	{
		while (hot[left] < a[i])
		{
			left++;
		}
		ans += ma[left];
	}
	cout << ans << endl;
	return;
}

int main()
{
	solve();
	return 0;
}

        第二种AC码

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
using namespace std;

int hot[100010];
int siz[100010];
int ma[100010];
int a[100010];
void solve()
{
	int n, t, max1 = 0, max2 = 0;
	cin >> n >> t;
	for (int i = n; i >= 1; i--)
	{
		cin >> hot[i];
	}
	for (int i = n; i >= 1; i--)
	{
		cin >> siz[i];
	}
	for (int i = 1; i <= n; i++)
	{
		if (siz[i] > max1)
		{
			if (siz[i] > max2)
			{
				max1 = max2;
				max2 = siz[i];
			}
			else
			{
				max1 = siz[i];
			}
		}
		ma[i] = max1 + max2;
	}
	hot[n + 1] = 1000000;
	ma[n + 1] = ma[n];
	for(int i = 1;i<=t;i++)
	{
		cin >> a[i];
	}
	sort(a + 1, a + t + 1);
	int left = 1;
	long long ans = 0;
	for (int i = 1; i <= t; i++)
	{
		while (hot[left] < a[i])
		{
			left++;
		}
		left--;
		if (left < 1)
			left = 0;
		ans += ma[left];
	}
	cout << ans << endl;
	return;
}

int main()
{
	solve();
	return 0;
}

盲目自大的小灰灰

        有一说一,C题我感觉甚至比B题简单

        题意分析

        这题就是先给你一个时间,然后再告诉你有几个陷阱,然后每个陷阱触发的时间在什么时候,触发后玩家此时不能在哪边,0代表左边,1代表右边

        随后给你t次查询,每次查询给你一个数,问你这个分数可不可能达到(得活着完成游戏),玩家每次向另一个方向跳跃,就会加一分

        能就输出Yes,不能就输出No


        逻辑梳理

        这题逻辑也很简单,先算出最小可能得分,再算出最大可能得分,然后后面每次查询得分时,就判断得分是不是在得分区间内,如果在区间外,就输出No,如果在区间内再进行判断,然如果t时间时有障碍物,那么就直接输出Yes,反之,就判断,分-最小分能不能被2整除,能就输出Yes,不能就输出No

        为什么呢,因为如果没有自由控制区间,那么,得分只会2分2分的变,如果有了自由控制区间,最小到最大的分值也就成了必然,这个画图就很容易理解了,就不过多阐述了

        接下来进入代码实现环节


        代码实现

        这里就直接放AC码啦

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <algorithm>
#include <math.h>
#include <queue>
using namespace std;

bool tim[200010];
bool wei[200010];

void solve()
{
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		int k, K;
		cin >> k >> K;
		tim[k] = 1;
		wei[k] = K;
	}
	bool fang = 0;
	int ma = 0;
	int mi = 0;
	int sui = 0;
	for (int i = 1; i <= n; i++)
	{
		if (tim[i] && fang == wei[i])
		{
			fang = !fang;
			mi++;
		}
	}
	fang = 0;
	for (int i = 1; i <= n; i++)
	{
		if (!tim[i])
		{
			fang = !fang;
			ma++;
			sui++;
		}
		else
		{
			if (fang == wei[i])
			{
				fang = !fang;
				ma++;
			}
			sui = 0;
		}
	}
	int t;
	cin >> t;
	while (t--)
	{
		int k;
		cin >> k;
		if (k >= mi && k <= ma)
		{
			if (sui != 0)
			{
				cout << "Yes" << endl;
			}
			else if ((k - mi) % 2)
			{
				cout << "No" << endl;
			}
			else
			{
				cout << "Yes" << endl;
			}
		}
		else
		{
			cout << "No" << endl;
		}
	}
	return;
}

int main()
{
	solve();
	return 0;
}

结语:

        今日算法讲解到此结束啦,希望对你们有所帮助,谢谢观看,如果觉得不错可以分享给朋友哟。有什么看不懂的可以评论问哦,

牛客练习赛142是一场编程竞赛,通常包含多个算法题目,涵盖如数组、字符串、链表、动态规划等常见数据结构与算法知识点。针对这类比赛的解题思路和方法,可以从以下几个方面进行分析: ### 题目类型与解题策略 1. **数组相关问题** - 常见的题目包括查找数组中出现次数超过一半的数字、寻找缺失的数字、求解最大子数组和等。 - 解题方法包括使用哈希表统计频率、摩尔投票法(适用于多数元素问题)、双指针技巧或前缀和优化。 2. **链表操作** - 链表题目可能涉及反转链表、判断链表是否有环、找出两个链表的相交节点等。 - 例如,在找两个链表相交点的问题中,可以先计算各自长度,然后让长链表先走差值步数,再同步遍历比较节点地址[^3]。 3. **字符串处理** - 包括最长回文子串、无重复字符的最长子串等。 - 可采用滑动窗口、动态规划或中心扩展法等策略。 4. **树与图** - 树相关的题目可能涉及二叉树的遍历、路径和、最近公共祖先等问题。 - 图论问题可能需要使用深度优先搜索(DFS)、广度优先搜索(BFS)或拓扑排序等算法。 5. **动态规划** - 动态规划常用于解决背包问题、最长递增子序列、编辑距离等。 - 关键在于定义状态转移方程,并通过迭代或记忆化搜索进行求解。 6. **贪心算法** - 适用于区间调度、活动选择、硬币找零等问题。 - 贪心策略的核心在于每一步都做出局部最优选择。 ### 示例代码:摩尔投票法解决“多数元素”问题 ```python def majorityElement(nums): count = 0 candidate = None for num in nums: if count == 0: candidate = num count += (1 if num == candidate else -1) return candidate ``` 该算法时间复杂度为 O(n),空间复杂度为 O(1),非常适合处理大规模输入的数据集[^2]。 ### 提升解题能力的建议 - **刷题积累经验**:在 LeetCode、Codeforces、AtCoder 等平台上持续练习,熟悉各种题型。 - **学习经典算法**:掌握常见的算法模板,如二分查找、归并排序、快速选择等。 - **阅读官方题解与讨论区**:了解不同解法的优劣,尤其是最优解的时间复杂度分析。 - **模拟比赛训练**:定期参加在线编程比赛,提升实战能力和代码调试速度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值