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\)

c++ 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)\)

c++ 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\)

c++ 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;
}

}
posted @ 2024-02-28 15:49  Ritsuki  阅读(124)  评论(0)    收藏  举报