写在前面
笔者是大二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题是我看了官方题解之后,想了半天,勉强理解的思路。实战中真的有人能想到这种抽象解法吗?我感觉这个题解实在是一点都不友好,反而民间大佬做的更清楚