2024牛客寒假算法基础训练营5

牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ (nowcoder.com)

注:本题解只涉及赛时思路和赛后补题收获,人菜佬勿喷

赛时七题【ACGHILM】补题【EFJKB】

这场...特判很多,非常需要动脑子。

A.mutsumi的质数合数

A-mutsumi的质数合数_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

签到题,只需要特判 1 这个数,因为 1 既不是质数也不是合数

代码:

void solve(){
    int n;
    cin >> n;
    vector<int>a(n);
    int sum = 0;
    for(int i = 0; i < n; i++){
        cin >> a[i];
        if(a[i] == 1)
            sum++;
    }
    cout << n - sum << endl;
}

C.anon的私货

C-anon的私货_2024牛客寒假算法基础集训营5 (nowcoder.cc

分析:

分析题意可以发现,每两个数为一组的话最多可以插入较小数 −1 个 0 ,但是,在这两个数插入 0 之后又会对下两个数产生影响,所以只需要用一个变量 m 来记录上两个数的较小值 −1 ,然后判断是否会对当前这两个数产生影响,如果不影响,再取第一个数减去上一组 0 的个数和第二个数的较小值 −1 即可,如果影响那么这组数不能加。

除此之外还要特判 1 这个数, n 等于 1 时直接输出第一个数 −1 即可

注意数据范围,记得开 longlong

代码:

void solve(){
    ll n;
    cin >> n;
    vector<ll>a(n + 2), b(n + 2);
    ll sum = 0, num = 0;
    for(ll i = 1; i <= n; i++){
        cin >> a[i];
    }
    if(n == 1){
        cout << a[1] - 1 << endl;
        return;
    }
    ll ans = a[1] - 1;
    b[0] = ans;
    for(int i = 1; i <= n - 1; i++){
        ll m = min(a[i], a[i + 1]) - 1;
        if(a[i] - 1 > b[i - 1])
            b[i] = min(a[i] - 1 - b[i - 1], m);
        else    
            b[i] = 0;
        ans += b[i];
    }
    if(a[n] - 1 > b[n - 1]){
        cout << ans + a[n] - 1 - b[n - 1] << endl;
        return;
    }
    cout << ans << endl;
}

G.sakiko的排列构造(easy)

G-sakiko的排列构造(easy)_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

这道题很有意思,搓一下样例可以发现答案必定是一段一段连续的数。

比如:1 2 3 4 5 6

可以先找大于6的质数,找到后倒序排列:6 5 4 3 2 1 可以发现 p_i+i都是7

但发现 1 2 3 4 5 6 7 8这组样例,大于8的最小质数是11,所以需要把8放在 i=4 的位置上倒序排列,那前三个该如何凑到11,再看题目给的样例:1 2 3可以排列成1 3 2,这个问题便解决了。

所以这道题的解题思路就是:先用欧拉筛筛出质数,然后凑出倒序的部分,再填补前半部分即可。

代码:

const int N = 5e6 + 10;
int p[N],cnt;
bool vis[N];
bool prime[N];
void init(int n){
    for(int i=2;i<=n;++i){
        if(vis[i]==0){
            p[cnt]=i;
            cnt++;
            prime[i]=true;
            for(int j=i+i;j<=n;j+=i) vis[j]=1;
        }
    }
}
void solve(){
    init(2e6 + 5);
    int n;    
    cin >> n;
    int l = 1;
    vector<int>a;
    int pos[N];
    for(int i = n; i >= 1; i--){
        while(prime[l+i] == 0 || pos[l] == 1){
            l++;
            if(l > n)    
                l = 1;
        }
        a.push_back(l);
        pos[l] = 1;
        l++;
        if(l > n)
            l = 1;
    }
 
    for(int i = a.size() - 1; i >= 0; i--)
        cout << a[i] << " ";
    cout << endl;
}

H.sakiko的排列构造(hard)

H-sakiko的排列构造(hard)_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

hard版本与easy版本不同的是数据范围不同,采用埃氏筛筛出质数后复杂度可以通过此题。

思路同G

代码:

同G

I.rikki的最短路

I-rikki的最短路_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

签到题。根据题意,需要先去T再去A。所以先判断A与T是否在同一侧,如果在同一侧,判断A与T的大小,A的绝对值大那么答案就是A的绝对值+A到T的距离,否则即为T。如果不在同一侧,判断A与K的大小关系,K大于A那么只能是先去A再去T,答案是2*A+T,否则只能先去T再去找A,答案是3*T+2*A

注意数据范围,记得开 longlong

代码:

void solve(){
    ll t, a, k;
    cin >> t >> a >> k;
    if(t < 0){
        if(a < 0){
            if(t > a){
                if(t - a > k)
                    cout << -t + 2 * (t - a);
                else
                    cout << -a + (t - a);
            }
            else
                cout << -t;
        }
        else{
            if(a > k){
                cout << 2 * a + 3 * (-t);
            }
            else
                cout << 2 * a + (-t);
        }
    }
    else{
        if(a > 0){
            if(t < a){
                if(a - t > k)
                    cout << t + 2 * (a - t);
                else
                    cout << a + (a - t);
            }
            else
              cout << t;  
        } 
        else{
            if(-a > k){
                cout << 2 * (-a) + 3 * t;
            }
            else
                cout << 2 * (-a) + t;
        }
    }
}

L.anon的星星

L-anon的星星_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

签到题。根据题意模拟即可。(赛时直接用数学式子交了一发,不知道哪写错了,喜提一发wa,老老实实暴力枚举去了

代码:

void solve(){
    int n, x;
    cin >> n >> x;
    for(int i = 0; i <= n; i++)
        for(int j = 0; j <= n; j++){
            if(i - j == x && i + j == n){
                cout << i << " " << j << endl;
                return;
            }
        }
    cout << -1 << endl;
}

M.mutsumi的排列连通

M-mutsumi的排列连通_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

这道题跟第一场的关鸡很像,关鸡分鸡(?

之所以说跟关鸡很像是因为这两道题需要判断的条件是类似的。(都是找同一列是否有相同的数或者相邻列不同行有没有相同的数字)如果不明白这个条件,传送门(第一场题解:2024牛客寒假算法基础训练营1 - 知乎 (zhihu.com)

分析可以发现,最多删两个即可将矩阵分成连通块,如果有满足条件的则只需要操作一次。

然后特判n为1 和 n为2的时候,n为1是一定不能完成的,n等于2的时候如果出现同一列的数字相同也是不能完成的,否则只需操作一次

代码:

void solve(){
    int n;
    cin >> n;
    vector<int>a(n + 2), b(n + 2);
    for(int i = 1; i <= n; i++){
        cin >> a[i];
    }
    for(int i = 1; i <= n; i++)
        cin >> b[i];
    if(n == 1){
        cout << -1 << endl;
        return;
    }
    if(n == 2){
        if(a[1] == b[1] || a[2] == b[2])
            cout << -1 << endl;
        else
            cout << 1 << endl;
        return;
    }
    for(int i = 1; i <= n; i++){
        if((a[i] == b[i] && i != 1 && i != n) || a[i] == b[i - 1] || a[i] == b[i + 1]){
            cout << 1 << endl;
            return;
        }
    }
    cout << 2 << endl;
}

E.soyorin的数组操作(easy)

E-soyorin的数组操作(easy)_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

不难发现当n为偶数的时候是一定可以完成的。所以我们只需要讨论n为奇数的情况

直接将数组从后往前遍历,每个数的操作的次数是可以根据当前的下标计算出来的

直接根据题意模拟一下 ,如果出现操作后还为降序的情况直接输出NO即可

注意数据范围记得开 longlong

代码:

void solve(){
    ll n;
    cin >> n;
    vector<ll>a(n + 1);
    for(ll i = 1; i <= n; i++)
        cin >> a[i];
    if(n % 2 == 0){
        cout << "YES\n";
        return;
    }
    ll pos = 0;
    for(ll i = n - 1; i > 1; i -= 2){
        a[i] += pos * i;
        a[i - 1] += pos * (i - 1);
        if(a[i] > a[i + 1]){
            cout << "NO\n";
            return;
        }
        ll t = (a[i + 1] - a[i]) / i;
        a[i] += t * i;
        a[i - 1] += t * (i - 1);
        pos += t;
    }
    if(is_sorted(a.begin(), a.end())){
        cout << "YES\n";
        return;
    }
    cout << "NO\n";
}

F.soyorin的数组操作(hard)

F-soyorin的数组操作(hard)_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

基本思路跟E题是一样的,n为偶数是一定能完成的,n为奇数时从后往前遍历数组并记录所需要的次数即可

代码:

void solve(){
    ll n;
    cin >> n;
    vector<ll>a(n + 1);
    for(ll i = 1; i <= n; i++)
        cin >> a[i];
    if(n & 1){
        ll cnt = 0;
        ll t = 0;
        auto b = a;
        {
            ll cnt = 0;
            for(ll i = n - 1; i >= 2; i -= 2){
                b[i] += cnt * i;
                b[i - 1] += cnt * (i - 1);
                if(b[i] > b[i + 1]){
                    cout << -1 << endl;
                    return;
                }
                ll t = (b[i + 1] - b[i]) / i;
                b[i] += t * i;
                b[i - 1] += t * (i - 1);
                cnt += t;
            }
            if(!is_sorted(b.begin(), b.end())){
                cout << -1 << endl;
                return;
            }
        }
        for(ll i = n - 1; i >= 1; i -= 2){
            a[i] += i * t;
            a[i - 1] += (i - 1) * t;
            if(a[i + 1] < a[i]){
                ll x = a[i] - a[i + 1];
                cnt -= x;
                t += x;
                a[i - 1] += x * (i - 1);
                a[i] += x * i;
                a[i + 1] += x * (i + 1);
                a[i + 2] += x * (i + 2);
            }
            else
                cnt += (b[i + 1] - a[i]) / i;
            ll x = a[i - 1] - a[i];
            if(x > 0){
                cnt -= x;
                t += x;
                a[i] += x * i;
                a[i - 1] += x * (i - 1);
            }
            if(cnt < 0){
                cout << -1 << endl;
                return;
            }
        }
        cout << t << endl;
    }
    else{
        ll cnt = 0;
        for(ll i = n ; i >= 1; i--){
            ll l = a[i - 1] + (i - 1) * cnt;
            ll r = a[i] + i * cnt;
            if(r < l)
                cnt += l - r;
        }
        cout << cnt << endl;
    }
}

J.rikki的数组陡峭值

J-rikki的数组陡峭值_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

赛时读错题,读成 a_i 在l_i 和 r_i 中选一个数,再求最小的陡峭值,那便是一个很简单的线性dp,

用二维数组 dp[i][0] 和 dp[i][1] 来分别表示第 i个数选第一个和第 i 个数选第二个

很容易得到转移方程:

dp[i][1]=min(dp[i−1][0]+l_i,dp[i−1][1]+l_i)

dp[i][0]=min(dp[i−1][0]+r_i,dp[i−1][1]+r_i)

但是这个题是在区间中选择一个数,分析陡峭值的定义,发现每两个数要想陡峭值越小,就需要取这两个区间的交集,所以我们用两个变量 l,r 来维护区间的交集,当出现相邻区间交集为空时就更新维护交集,当把交集维护到一个数组中时,不难发现,这个题就变成了读错题的dp形状。

代码:

void solve(){
    ll n;
    cin >> n;
    vector<pair<ll, ll>>a(n);
    for(int i = 0; i < n; i++)
        cin >> a[i].first >> a[i].second;
    ll r = 1e9, l = -1;
    vector<vector<ll>>v;
    for(int i = 0; i < n; i++){
        if(r < a[i].first || l > a[i].second){
            v.push_back({l, r});
            l = a[i].first;
            r = a[i].second;
        }
        l = max(l, a[i].first);
        r = min(r, a[i].second);
    }
    v.push_back({l, r});
    vector dp(v.size(), vector(2, 0LL));
    for(int i = 1; i < v.size(); i++){
        dp[i][1] = min(dp[i - 1][0] + abs(v[i][1] - v[i - 1][0]), dp[i - 1][1] + abs(v[i][1] - v[i - 1][1]));
        dp[i][0] = min(dp[i - 1][0] + abs(v[i][0] - v[i - 1][0]), dp[i - 1][1] + abs(v[i][0] - v[i - 1][1]));
    }
    cout << min(dp.back()[0], dp.back()[1]) << endl;
}

K.soyorin的通知

K-soyorin的通知_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

赛时没看,赛后发现是个很经典的完全背包问题,用 dp[i] 表示传播给 i 个人花费的代价

状态转移方程与普通完全背包类似

f_{i,j}=min(f_{i,j},f_{i,j}b_i+a_i)

这个状态转移方程是对花费 a_i 传播给 b_i 个人的

题目还有种可能就是花费 p 传给1个人,用一个循环枚举这种方式的个数,最后取最小值即可

代码:

void solve(){
    ll n, p;
    cin >> n >> p;
    vector<ll>dp(n + 1, 1e9), a(n + 1), b(n + 1);
    ll ans = 1e18;
    dp[0] = 0;
    for(ll i = 0; i < n; i++){
        cin >> a[i] >> b[i];
        if(b[i] >= n)
            b[i] = n - 1;
    }
    for(ll i = 0; i < n; i++){
        for(ll j = 0; j <= n; j++){
            dp[j] = min(dp[j], dp[max(0LL, j - b[i])] + a[i]);//花费ai的情况
        }
    }
    for(ll i = 0; i < n; i++){//枚举花费p传给1个人的个数时花费的代价
        ans = min(ans, dp[i] + (n - i) * p);
    }
    cout << ans << endl;
}

B.omorin的字符串迷茫值

B-tomorin的字符串迷茫值_2024牛客寒假算法基础集训营5 (nowcoder.com)

分析:

"mygo"字串能够在固定在删除的方式下形成只有这八种:

∗ 可以代表任意字符,这八种只能通过删除 ∗ 来得到,所以方案数是1,考虑每种字串只需要计算该字串前后有多少种删除方式即可。

摸一下题意可以发现:删除长度为1的字符方案数是1,删除长度为2的字符方案数是2,删除长度为3的字符方案数是3,删除长度为4的字符方案数为5,删除长度为5的方案数为8......

可以发现是一个斐波那契数列,所以这个题只需要枚举字符串每个点,检查这个点后的字符串能否形成八种字串中的一种,如果能的话只需将答案加上该字串的前后任意删除方案数之一即可。

代码:

void solve(){
    string s;
    cin >> s;
    int n = s.size();
    vector<string>a = {"mygo", "m*ygo", "my*go", "myg*o", "m*y*g*o", "m*y*go", "m*yg*o", "my*g*o"};
    vector<ll>dp(n + 1);
    dp[0] = 1;
    dp[1] = 2;
    dp[2] = 3;
    int ans = 0;
    for(int i = 3; i <= n; i++)
        dp[i] = (dp[i - 1] + dp[i - 2]) % mod;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < 8; j++){
            int flag = 1;
            for(int k = 0; k < a[j].size(); k++){
                if(a[j][k] == '*')
                    continue;
                if(s[i + k] != a[j][k]){
                    flag = 0;
                    break;
                }
            }
            if(flag)
                ans = (ans + (dp[i] * dp[n - i - a[j].size()]) % mod) % mod;
        }
    }
    cout << ans << endl;
}
  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值