算法沉淀第八天(牛客周赛 Round 114 和 Incremental Stay) - 详解

目录

引言:

牛客周赛 Round 114

        小彩找数

        题目分析

        逻辑梳理

        代码实现 

        小彩的好字符串

        题目分析

        逻辑梳理

        代码实现

        小彩的字符串交换

        题目分析

        逻辑梳理

        代码实现

        小彩的数组选数

        题目分析

        逻辑梳理

        代码实现

Incremental Stay

        题目分析

        逻辑梳理

        代码实现

结语:


引言:

        今天是算法沉淀的第八天,因为昨天只打了一场比赛,所以今天总结完有多余时间再开一道CF1400的题

        昨天的牛客周赛 Round 114的战绩如下

        之后题目链接就不放啦,就直接放题目的图啦

        那么,话不多说,我们就进入今天的算法讲解———————>


牛客周赛 Round 114

        小彩找数

        题目分析

        这题就是问你1,2,3这三个数分别出现在数组的哪个位置,然后输出位置就好了


        逻辑梳理

        这纯水题,就循环3次每次都找一个数就好了


        代码实现 

        这里就直接放代码啦

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
void solve()
{
	int a[4];
	for (int i = 1; i <= 3; i++)
		cin >> a[i];
	for (int i = 1; i <= 3; i++)
	{
		for (int j = 1; j <= 3; j++)
		{
			if (a[j] == i)
			{
				cout << j << " ";
				continue;
			}
		}
	}
}
int main()
{
	solve();
	return 0;
}

        小彩的好字符串

        题目分析

        这题就是给你一个长度为n的字符串,然后问你这个字符串中有几个连续子串满足既存在1,也存在2,也存在3,并且1,2,3的个数一样

        输出满足条件的连续子串个数就可以了


        逻辑梳理

        这题因为数据范围给的很小,所以开俩层循环把所有的连续字串都分析时间复杂度都不会超

        那么在循环前,我们可以用前缀和对字符串先进行预处理,分别算出1,2,3出现次数的前缀和。

        然后在判断子串的时候只要通过1,2,3的前缀和数组来判断是不是满足条件就可以了

        然后循环结束后把结果输出就可以啦

        逻辑梳理完了,接下来我们来看代码


         代码实现

        这里就直接放源码啦

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int yi[2010];
int er[2010];
int san[2010];
void solve()
{
	int n;
	cin >> n;
	string a;
	cin >> a;
	for (int i = 0; i < n; i++)
	{
		if (a[i] == '1')
		{
			yi[i + 1] = yi[i] + 1;
			er[i + 1] = er[i];
			san[i + 1] = san[i];
		}
		if (a[i] == '2')
		{
			yi[i + 1] = yi[i];
			er[i + 1] = er[i]+1;
			san[i + 1] = san[i];
		}
		if (a[i] == '3')
		{
			yi[i + 1] = yi[i];
			er[i + 1] = er[i];
			san[i + 1] = san[i]+1;
		}
	}
	long long ans = 0;
	for (int i = 3; i <= n; i++)
	{
		for (int j = 1; j <= i; j++)
		{
			if (yi[i] - yi[j - 1] == er[i] - er[j - 1] && er[i] - er[j - 1] == san[i] - san[j - 1])
			{
				ans++;
			}
		}
	}
	cout << ans << endl;
}
int main()
{
	solve();
	return 0;
}

        小彩的字符串交换

        题目分析

        这题就是给你一个字符串,然后你可以选择任意俩个下标,交换这俩个下标对应的值,然后需要在操作n次后,字符串中存在长为3的连续子串,且该子串中有1,有2,也有3

        如果可以得到满足条件的连续子串,就输出最少的操作次数

        如果得不到满足条件的连续子串,就输出-1;

        那么题目分析完了,接下来就进入逻辑梳理环节


        逻辑梳理

        我们可以先看输出-1的情况,什么情况下会输出-1呢,就是1,2,3这三个有一个及以上没有出现在字符串里

        如果字符串里,1,2,3都出现过,那肯定是能操作出满足条件的子串的

        那么怎么操作呢,我们从1开始统计长度为3的子串中元素情况,如果都一样,就是要换2次,如果有一个不一样就是要换1一次,如果都不一样,就是不用换,这个时候直接结束循环就好了

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


        代码实现

        这里就直接放代码啦

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int t;
void solve()
{
	bool vis[4] = { 0 };
	int n;
	string a;
	cin >> n;
	cin >> a;
	int butong = 0;
	for (int i = 0; i < n; i++)
	{
		vis[a[i] - '0'] = 1;
		if (i >= 2)
		{
			if (a[i] == a[i - 1] && a[i] == a[i - 2] && a[i - 1] == a[i - 2])
			{
				butong = max(butong, 1);
			}
			else if (a[i] == a[i - 1] || a[i] == a[i - 2] || a[i - 1] == a[i - 2])
			{
				butong = max(butong, 2);
			}
			else
			{
				butong = 3;
				break;
			}
		}
	}
	if (vis[1] + vis[2] + vis[3] != 3)
	{
		cout << "-1" << endl;
		return;
	}
	cout << 3 - butong << endl;
	return;
}
int main()
{
	cin >> t;
	while (t--)
		solve();
	return 0;
}

        小彩的数组选数

        题目分析

        这题就是给你一个数组,然后你每次可以选择一个位置,得分加上这个位置的元素,然后将这个位置以及相邻位置的元素变成0,直到数组全部变成0

        问你得分最高可以是多少,输出最高可能得分就可以了

        那么题目分析完了,接下来我们进入代码实现环节


        逻辑梳理

        这题我们直接从整个数组来看很难想到怎么找,那么我们就来把数组从小到大扩大,看看能不能找出一点规律

        首先,如果数组长度为1,那肯定得分最多就是a[1]

        那么到数组长度为2的时候,得分最多肯定就是max(a[1],a[2]),因为不管取哪个,另一个都会变成0(相邻)

        那数组长度为3时候,就会有俩种情况了,就是max(a[1]+a[3],a[2])

        因为如果取1和3位置的元素,影响的只是2位置的元素变成0,而如果取了2位置的元素,那么1和3位置的元素就都变成0了,所以就有了这个式子

        推到这,基本就已经可以知道了一个迭代的式子

        我们先设个数组ans,用来存长度不同时所能达到的最大值

        首先把ans[1]和ans[2]初始化成a[1]和max(a[1],a[2]),然后从3开始循环,进行迭代

   ans[i]=max(ans[i-1],ans[i-2]+a[i])        

      为什么是这个式子呢,ans[i-1]是确保从0到i-1的数组全是0的时候都最大得分,ans[i-2]是确保从0到i-2的数组全是0的时候最大得分情况,因为i-1位置选与不选会影响到i位置的元素是否存在,而i-2位置元素的选与不选并不会影响到i的情况,所以会加上a[i]的值

        如果i-1位置不选的话,那ans[i-1]和ans[i-2]就一样了,所以不会影响ans[i]的最终值

        如果i-1位置选的话,那i位置肯定变成0了,所以也不用加a[i]。

        据此,这个式子就成立了

        只需要一直迭代直到循环结束,输出ans[n]就可以了

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


        代码实现

        这里就直接放代码啦

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int a[200010];
long long ans[200010];
void solve()
{
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	if (n == 1)
	{
		cout << a[1] << endl;
		return;
	}
	ans[1] = a[1];
	ans[2] = max(a[1], a[2]);
	for (int i = 3; i <= n; i++)
	{
		ans[i] = max(ans[i - 2] + a[i], ans[i - 1]);
	}
	cout << ans[n] << endl;
}
int main()
{
	solve();
	return 0;
}

        到此,牛客周赛的题目就讲完啦,接下来我们来看一道CF1400的题


Incremental Stay

        题目分析

        这题意思就是给你一个数n表示有多少人进出博物馆,然后再输入2×n个数,这些数表示在这个时间,有人经过了博物馆门(是进是出不知道)

        然后输出博物馆容纳不同人数(1——n)时,输出所有人呆在博物馆时间加起来的最大值就可以了

        那么题目就分析完了,接下来我们进入逻辑梳理环节


        逻辑梳理

        首先我们先来博物馆只能容纳一个人的情况,这时候只有进出进出进出的情况

        然后我们再来看能容纳俩个人的情况,这情况下只需要让1个人进去后就一直待到最后再走,然后另一个进出进出进出,就能得到时间最大值了

        然后我们再来看容纳三个人的情况,那么只要先让一个人进去待到最后走,那么又变成了俩个人的情况。以此类推,所有的情况就都适配了

        所以如果是容纳n个人的时候,就先让n-1个人依次进去,然后待到最后依次出来,然后剩余的一个人就进出进出进出就好了

        那么这题的逻辑就梳理完啦,接下来我们进入代码实现环节


        代码实现

        首先是进出进出的表示,如果每次都算的话大概率是会超时,那么我们可以先用前缀和数组把数据先预处理完,因为分为从奇数下标进和偶数下标进,所以需要俩个前缀和来分别表示奇数进和偶数进的时候

        然后最里面的人的时间处理完了,接下来我们要处理剩余n-1个人的时间

        因为第一次是最边边俩个相加,第二次是第二层相加,第三次是第三层相加,所以我们可以在循环的时候直接迭代就可以了,这个具体讲比较讲不清楚,直接看代码吧,代码表示的还是很好理解的

        前缀和版本

        那么,接下来就直接放AC码啦

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int t;
long long a[400010];
long long ji[200010];
long long ou[200010];
void solve()
{
	int n;
	cin >> n;
	for (int i = 1; i <= 2 * n; i++)
	{
		cin >> a[i];
	}
	for (int i = 1, j = 1; i <= n; i++, j += 2)
	{
		ji[i] = ji[i - 1] + a[j + 1] - a[j];
	}
	for (int i = 1, j = 2; i < n; i++, j += 2)
	{
		ou[i] = ou[i - 1] + a[j + 1] - a[j];
	}
	long long ans = 0;
	cout << ji[n] << " ";
	for (int i = 2; i <= n; i++)
	{
		ans += a[n * 2 - i + 2] - a[i - 1];
		if (i % 2)
		{
			cout << ans + ji[n - i / 2] - ji[i / 2] << " ";
		}
		else
		{
			cout << ans + ou[n - i / 2] - ou[i / 2 - 1] << " ";
		}
	}
	cout << endl;
	return;
}
int main()
{
	cin >> t;
	while (t--)
		solve();
	return 0;
}

        但是这题其实是可以优化的,我也是今天思考了一下灵机一动发现的,优化后的代码如下,下面的这个代码就不需要用到前缀和,也少了几个循环,时间和空间复杂度都降下去了很多,具体就看下面代码

        优化版本(不用算法,只用迭代)

        这个代码对迭代的运用要求有点高,实际还是跟上面一个逻辑,如下

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
int t;
long long a[400010];
void solve()
{
	int n;
	cin >> n;
	long long lin = 0;
	for (int i = 1; i <= 2 * n; i++)
	{
		cin >> a[i];
		if (i % 2)
		{
			lin -= a[i];
		}
		else
		{
			lin += a[i];
		}
	}
	long long ans = 0;
	for (int i = 1; i <= n; i++)
	{
		cout << lin + ans << " ";
		lin = a[2 * n - i + 1] - a[i] - lin;
		ans += a[n * 2 - i + 1] - a[i];
	}
	cout << endl;
	return;
}
int main()
{
	cin >> t;
	while (t--)
		solve();
	return 0;
}

        这俩种代码的时间和空间复杂度如下图,主要是空间降得多

        那么这题就讲解完啦


结语:

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

posted @ 2025-11-20 10:50  yangykaifa  阅读(4)  评论(0)    收藏  举报