2024牛客寒假算法基础集训营5个人补题题解(B、E、F、K)
比赛链接:2024牛客寒假算法基础集训营5
C、anon的私货
贪心。考虑在每两个数中间最多可以插多少数,与\(a[i]\)相邻的左右两边最多可以插\(a[i]-1\)个\(0\),那么\(a[i]和a[i+1]\)之间最多可以插\(min(a[i],a[i+1])-1\)个\(0\)。
预处理每个数相邻两边能插多少个\(0\),分两种情况讨论:
①\(a[1]\)前面插\(a[1]-1\)个\(0\)
②\(a[1]\)前面不插\(0\)
每次插完之后更新\(a[i+1]\)左右两边能插的0的数量,全部处理完以后如果\(a[n]\)两边还能插\(0\)就把\(0\)都插到数组最后。
两种情况取\(max\)
AC代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=4e5+10;
const int inf=1e18;
const int M=1e9+7;
string ans;
int cnt[N];
int a[N];
signed main() {
int n;
int ans=0;
int ans2=0;
cin >>n;
for(int i=1;i<=n;i++)
cin >>a[i],cnt[i]=a[i]-1;
for(int i=1;i<=n;i++)
{
if(cnt[i]>=cnt[i+1])
{
cnt[i]-=cnt[i+1];ans+=cnt[i+1];cnt[i+1]=0;
}
else
{
cnt[i+1]-=cnt[i];ans+=cnt[i];cnt[i]=0;
}
}
for(int i=1;i<=n;i++)
cnt[i]=a[i]-1;
ans2=cnt[1],cnt[1]=0;
for(int i=1;i<=n;i++)
{
if(cnt[i]>=cnt[i+1])
{
cnt[i]-=cnt[i+1];ans2+=cnt[i+1];cnt[i+1]=0;
}
else
{
cnt[i+1]-=cnt[i];ans2+=cnt[i];cnt[i]=0;
}
}
ans2+=cnt[n];
ans+=cnt[1]+cnt[n];
cout<<max(ans,ans2)<<'\n';
}
E&F、soyorin的数组操作
还是贪心。
两题的共用思路是判断数组能否变成非降序。首先明确一点,取完任何一个k并进行一次加的操作后,从1到k位置的每个数和前一个数的差值就会加大1,因此当\(n\)是偶数时,肯定能在数次操作之后使得对于每一个i而言\(a[i]-a[i-1]\geq0\)。
而n是奇数的话就要考虑了,因为最后一个数必定不在操作范围内,约等于把对\(a[n-1]\)的操作次数定死了,而我们既然让前面的\(a[i-1]-a[i]\)之间的差值尽量变成非正数,就把\(a[n-1]\)一直加到它能允许的最大数,用\(cnt\)计数。从后往前扫,如果遇到偶数位置还有继续加的空间就进行重复操作,操作完以后判断是否为非降序就行。
剩下的问题就是最小操作次数的机会了,对于可以操作成非降序的数组而言,原先数组中相邻的两个数字,要想让前后两个数字变为非降序至少需要操作\(a[i-1]-a[i]\)次。那就直接在可操作范围内找最大的那个\(a[i-1]-a[i]\)就可以解决。
时间复杂度:\(O(n)\)
AC代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
const int inf=1e18;
const int M=1e9+7;
int a[N];
int b[N];
signed main() {
int t;
cin >>t;
while(t--)
{
int n;
int f=0;
cin >>n;
for(int i=1;i<=n;i++)
cin >>a[i];
for(int i=1;i<=n;i++)
b[i]=a[i];
if(n%2==0)
f=1;
else
{
int flag=0;
int cnt=0;
for(int i=n-1;i>=1;i--)
{
a[i]+=cnt*i;
if(i%2==0&&a[i]<a[i+1])
{
cnt+=(a[i+1]-a[i])/i;
a[i]+=(a[i+1]-a[i])/i*i;
}
if(a[i]>a[i+1])
flag=1;
}
if(flag==0)
f=1;
}
if(f==0)
{
cout<<"-1"<<'\n';
}
else
{
int ans=0;
for(int i=1;i<=(n%2==0? n-1:n-2);i++)
ans=max(ans,b[i]-b[i+1]);
cout<<ans<<'\n';
}
}
return 0;
}
K、soyorin的通知
第一眼可能看不出来但是个多重背包的模板题。定义\(dp[i]\)为传给至少\(i\)个人所需要的最少代价,因为\(n\)最多只有1000,\(b[i]\)的范围就算再大也可以视为\(1000\)以内。
按照多重背包处理完后\(dp[n-1]\)即是至少传给\(n-1\)个人所需要的最少代价,但还有一种情况需要考虑:即先用了不止一次的p,再开始考虑背包做法。
处理完多重背包后枚举使用\(p\)的次数,答案取最小值\(+p\)。
AC代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
const int inf=1e18;
const int M=1e9+7;
int a[N];
int b[N];
int dp[N];
signed main() {
int n,p;
cin >>n>>p;
for(int i=1;i<=n;i++)
cin >>a[i]>>b[i];
for(int i=1;i<=n;i++)
b[i]=min(n-1,b[i]);
for(int i=1;i<=1e6;i++)
dp[i]=inf;
dp[0]=0;
if(n==1)
{
cout<<p<<'\n';
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<=1010;j++)
{
if(dp[max(0ll,j-b[i])]!=inf)
dp[j]=min(dp[j],dp[max(0ll,j-b[i])]+a[i]);
}
}
int ans=dp[n-1];
for(int i=0;i<=n-1;i++)
{
ans=min(ans,dp[i]+(n-1-i)*p);
}
cout<<ans+p;
return 0;
}
}

浙公网安备 33010602011771号