2026年寒假牛客训练赛补题(三)

0 阅读7分钟

写在前面

笔者是大二ACM选手,正在参与牛客寒假训练赛,今天补的是2月7号场

这场难度整体偏简单,这也是我打的最好的一场,以六题收尾,最后是一千名出头

今天补一道我赛时过了但是难度虚高的题,还有一道赛时没过但是难度标注没有第一题高的题

注:本篇博客题面来自于牛客训练赛,题解参考牛客竞赛

训练赛网址:ac.nowcoder.com/acm/contest…

题解网址:ac.nowcoder.com/discuss/160…

F

题面:

                                       注:题面取自牛客竞赛,仅用于学习补题使用

题意解释:有一个两行n列的图,小红和小紫轮流防止障碍物,限制是不能把路堵死

最后小红要使从左上角到最右边一列的路径最短,小紫要使这条路最长,小红先手

问若两人都是最优策略,路径长度是多少

笔者的思路:

图只有两行,所以其实很好考虑,要求不能把路堵死,而且不能斜着走,所以障碍物至少要隔一个格子才能放

小红放障碍物的目的是阻止小紫放阻碍的障碍物,小紫放障碍物的目的是使路径变长

而两行的图,能使路径变长的方式无非只有逼迫其拐弯

所以最终结果必然是:n-1(走的列数)+小紫放的障碍物个数(因为小紫放的障碍物肯定是逼迫其拐弯的)

因为小红先手,所以主动权在小红手上,它可以限制小紫放障碍物的位置

这里画一个图简单想一下:

&代表起点,p代表紫,r代表红,问题核心就是分配r的放置使p的放置最少

由限制可知,r放的周边两列都是不能再放的,否则就把路堵死了

先讨论r的放置,毫无疑问,r在能干扰到p的前提下,放置越分散越好,所以看r隔一个、隔两个、隔三个放置的情况是什么样的

隔一个的情况:

这样是符合条件的,但是不是最优呢?

隔两个的情况:

我们惊奇地发现,若r与p的间隔是2的话,p依旧不能放在这个间隔里,而让r的放置更加分散这个目标却是实现了

隔三个的情况:

很遗憾,如果这样放置的话,p完全可以在不把路堵死的情况下在间隔放一个障碍物

所以r离p间隔2的策略是最优的,p离r1个无疑是最优的,这样两者都实现了最优策略

我们可以总结出规律了:在长度大于3的情况下,每隔5个才能出现一个p,所以我们可以算出一共有几个p,答案就是n-1+p

在长度小于等于4的时候呢?

很明显,在这种情况下,r只要放第三列,p完全没有放置空间,所以这样就是直接n-1

代码 :

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

void solve() 
{
	ll n;
	cin>>n;
	if(n<=4)
	{
		cout<<n-1<<endl;
		return;
	}
	ll len=n-2;         
	ll cycle=len/5;      
	ll remain=len%5;    
	ll blue=cycle;
	if(remain>=3) blue++;
	cout<<n-1+blue<<endl;
}

int main() 
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	ll t=1;
	cin>>t;
	while(t--) 
	{
		solve();
	}
	return 0;
}

C

题面

题意解释

这个题面很好理解,就是选择一段子序列,限制这个子序列任意两个相邻元素都不相同,将该子序列内1变0,0变1,问操作多少次可以使字符串任意相邻元素都不相同

思路

这道题我这里提供两种思路,一种是官方题解,这种思路我其实没看懂,只能把大概理解放上来

另一种是我看到的民间解法,这种方法我感觉很合理

官方解法:

最终状态只有两种:01010101……或10101010……

我们分两次来计算,然后取更小的操作数

先算01010101……的操作数

这样,奇数1偶数0(下标从0开始),先找到已经正确的,拼成一个新字符串

这样找出来的字符串可能是:1111111111111100、0101111111110000000000000000、1111111111

这种任意的都有可能

当这个新字符串有连续的1时,可以想到原字符串必然是奇数位正确,偶数位不正确,连续的0相反,而新字符串中1010交替说明原字符串也是这般美好。

而一次操作要求让“010101”这种01交替格式的子序列01互换,就是让原来在奇数的1跑到偶数位,或原来在偶数位的1跑去奇数位,0同理,而我们为了尽快让原字符串变成我们想要的,我们能做的就是这样改0和1的奇偶排布,但是因为能力有限,我们一次最多只能让一段的01奇偶互换,也就是让奇数位和偶数位的优势差减1。所以已经正确的字符串的奇数位和偶数位最大的优势差就是答案

具体算法就是设出现一个1加1,出现一个0减1,算所有子段和中的最大子段和和最小子段和,最大子段和就是1的最大优势,最小子段和就是0的最大优势,两者绝对值的比较,就是最需要改变的那一段的优势差,一次操作最多消除一个优势差,所以最大优势差就是操作数

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD=1e9+7;

ll fun(string s)
{
	int mx=0,mn=0,now=0;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]=='1') now++;
		else now--;
		if(now<0) now=0;
		mx=max(mx,now);
	}
	now=0;
	for(int i=0;i<s.size();i++)
	{
		if(s[i]=='1') now++;
		else now--;
		if(now>0) now=0;
		mn=min(mn,now);
	}
	return max(mx,abs(mn));
}

void solve() 
{
	ll n;
	cin>>n;
	string s,right;
	cin>>s;
	right="";
	for(int i=0;i<n;i++)
	{
		if((i&1)&&s[i]=='1') right+=s[i];
		if(!(i&1)&&s[i]=='0') right+=s[i];
	}
	ll ans=fun(right);
	right="";
	for(int i=0;i<n;i++)
	{
		if((i&1)&&s[i]=='0') right+=s[i];
		if(!(i&1)&&s[i]=='1') right+=s[i];
	}
	ans=min(ans,fun(right));
	cout<<ans<<endl;
}

int main() 
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	ll t=1;
	cin>>t;
	while(t--) 
	{
		solve();
	}
	return 0;
}

民间解法:

最终状态还是两种,一种10101010……,一种01010101……

将1的贡献算作1,0的贡献算作0,然后计算两种最终状态的前缀和

然后再看原数组,用同样的方法计算前缀和

算出期待状态减去原数组前缀和的数组,最终答案就是算出来的数组中的max-min

举个例子:

1010101前缀和是1122334,1101101是1223445,差分是0101111,答案就是1-0=0

现在来证明一下正确性:

1122334是最终状态,如果比这个小,说明0多了,如果比这个大,说明1多了

这里的max和min都是可正可负,而max-min就是1多的个数-0多的个数,因为每一步最多只能扭转一个1和0的优势差,所以这样得到的就是结果

因为计算前缀和和差分的代码比较简单,这里就不附了

篇末总结

这一场的题相对简单一点,我能做的就很多,最后一小时把F过了之后,C题应该是想不到了

C题是我看了官方题解之后,想了半天,勉强理解的思路。实战中真的有人能想到这种抽象解法吗?我感觉这个题解实在是一点都不友好,反而民间大佬做的更清楚