Codeforces Round 901 Div2 A-D题解

在这里插入图片描述

闲聊

原本是打算出最近那场Round 1021 Div2的题解的,但是没发现时间在下午4点没打上。后面补题的时候在B题因为题面解释不清晰还有样例解释部分有点问题卡住了,只做了A和C(第一次见比赛的题面没有具体解释或者举例解释的,就这还有人洗)

后面就做了这场Div2,总的来说还是挺简单的,前四题个人认为都还好(就是C数论不是很有经验卡了一下),E一下子从1600跳到2400就……留给以后的自己吧、

题解部分

A. Jellyfish and Undertale
给你三个数字a,b,n,然后给你一个n长度的数组c,每秒b都会-1,当b变为0的时候结束,每秒都可以使用 c i c_i ci进行操作,每次操作都会让当前的b加 c i c_i ci,最多加到a,如果进行了操作那么会先让b加 c i c_i ci然后再减一,每个 c i c_i ci只能进行一次操作,问你最多能让时间延续到多少秒

签到题,直接贪心计算就好

#include<bits/stdc++.h>

using namespace std;

#define int long long
#define lowbit(x) x&(-x)

const int MOD=1e9+7;
const int N=1000100;

int a[N];
int b[N];

void solve() {
    init();

    int maxvalue,now,n;
    cin>>maxvalue>>now>>n;
    int ans = 0;
    for(int i=1;i<=n;i++) cin>>a[i],ans += min(a[i],maxvalue-1);
    cout<<now + ans<<"\n";
}

signed main() {
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);

    //for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";

    int t=1;
    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

B. Jellyfish and Game
给定三个数n,m,k,然后给你一个长度为n的数组a和一个长度为m的数组b,然后有k次操作,对于第i次操作,如果i为奇数,那么第一个人可以选择a数组里面的一个数和b数组里面的一个数进行交换(可以选择不交换),i为偶数时,第二个人同理。两个人都想尽量让自己的数组总和变大,如果两个人操作合理,那么a数组总和为多少

很好想的一个博弈,我们只考虑一次操作的时候,对于第一个人而言,肯定是如果赚就将a数组里的最小值和b数组的最大值交换,那么我们根据博弈的经验,递推k为奇数的时候就会知道,之后无论第二个人做什么操作,我都跟着做一次,那么就相当于第二个人没有进行操作

如果有两次操作呢,首先第一个人肯定管不了第二个人的操作的,所以第一个人只能尽可能让自己更赚,所以第一个人还是会将a数组里的最小值和b数组里的最大值进行交换,如果赚的话。轮到第二个人的时候就要注意一点:此时a数组里有a本身的最大值以及b数组原本的最大值,b数组里有b数组本身的最小值和a数组原本的最小值,第二个人想尽可能赚肯定要前两者的最大值和后两者的最小值进行交换,之后无论第一个人进行什么操作第二个人都跟着做一遍,相当于第一个人没有操作

可以好好琢磨这里的逻辑
代码如下:

#include <iostream>

using namespace std;

#define int long long

const int N=1000100;
const int MOD=1e9+7;

int a[N];
int b[N];

void solve()
{
    int n,m,k;
    cin>>n>>m>>k;
    int maxvalue1=0,minvalue1=MOD;
    int maxvalue2=0,minvalue2=MOD;
    int sum1=0,sum2=0;

    for(int i=1;i<=n;i++) cin>>a[i],maxvalue1=max(maxvalue1,a[i]),minvalue1=min(minvalue1,a[i]),sum1+=a[i];
    for(int i=1;i<=m;i++) cin>>b[i],maxvalue2=max(maxvalue2,b[i]),minvalue2=min(minvalue2,b[i]),sum2+=b[i];


    if(k%2)
    {
        if(minvalue1 < maxvalue2) sum1 += maxvalue2 - minvalue1,sum2-=maxvalue2-minvalue1;
    }
    else
    {
        if(minvalue1 < maxvalue2) sum1 += maxvalue2 - minvalue1,sum2-=maxvalue2-minvalue1;
        int swapnum1 = max(maxvalue1,maxvalue2);
        int swapnum2 = min(minvalue1,minvalue2);
        if(swapnum1 > swapnum2) sum1 -= swapnum1 - swapnum2,sum2+=swapnum1-swapnum2;
    }
    cout<<sum1<<"\n";
}

signed main()
{
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();
    }
    return 0;
}

C. Jellyfish and Green Apple
给你n个苹果和m个人,你每次操作都可以选择一片苹果将其切成两半,注意重量会变成原本的一半,问你最少需要多少操作次数可以让m个人分到重量总和相等的苹果,如果不能输出-1

个人认为C > D

如果当前的n能被m整除,那么我们肯定是可以直接分的。
如果不能,由于我们要保证每个人分到的重量总和相等,所以我们要保证每个重量的苹果数量都能被m整除,所以如果n大于m我们就让n=n%m,反之就要对所有苹果进行操作,也就是让苹果数量加倍,直到n > m为止

那么如何判断是否最终是能让所有人分到重量总和相同的苹果呢

很明显,在我们n < m的时候,我们能进行的操作就只有让苹果数量乘二,而我们想要出现在不断乘二之后能够让n 是 m 的倍数 , 那么就意味着n乘若干个2得到m的倍数,反过来讲,n是m除去所有2之后的数的倍数,如果不是,那么必定不能得到想要的结果,反之就可以不断迭代寻找结果

可能有的人就有疑问了,可能就想问:有没有一种可能,我这次迭代的时候并不满足条件,但是在我求余之后就能满足条件了呢?

正如我所说的,我所要的是求余之后的结果是除掉全部2之后的m的倍数,而我求余之后的操作也就只能乘二而已,并不会改变其他任何地方,所以只要n不是m除去所有2之后的倍数,就不能得到想要的结果

代码如下:

#include<bits/stdc++.h>

using namespace std;

#define int long long
#define lowbit(x) x&(-x)

const int MOD=1e9+7;
const int N=1000100;

int a[N];
int b[N];

void solve() {
    init();

    int n,m;
    cin>>n>>m;
    //如果我进行了操作,原本是1kg的苹果的数量就要能够是m的整数倍,其中数量可以为0
    //如果当前数量少于m,那么就要对全部的苹果进行操作
    //当前数量是m的整数倍的时候,就可以不用继续操作,从而保证所有重量的苹果的数量都是m的整数倍
    //如果没有,那么就要进行求余操作,然后求余出来的结果继续递归处理
    //那么如何判定无法评分的情况
    //我的每次操作都是进行一个乘二的操作,也就是说我一直进行乘二操作之后能够让这个数字变成m的倍数
    //也就是说如果将m的所有2去掉之后,那么求余之后的结果就必须是去掉所有2之后的m的整数倍

    int mm = m;
    while(mm % 2 == 0) mm /= 2;

    int sign = 1;
    int ans = 0;
    while(1) {
        if(n % mm != 0) {
            sign = 0;
            break;
        }

        while(n < m) ans += n,n*=2;

        if(n % m == 0) break;
        n = n % m;
    }
    if(sign) cout<<ans<<"\n";
    else cout<<-1<<"\n";
}

signed main() {
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);

    //for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";

    int t=1;
    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}

D. Jellyfish and Mex
给你n个非负数的数组a,以及一个初始值为0的变量x

每次操作你都可以选择去掉a数组里的一个数字,然后x加上a数组的mex值,问你去掉a数组所有数字之后x最小值为多少

这里属于我被我自己坑了一把,之前说见到mex就考虑贪心,但是这道题很明显的不是贪心,因为我去掉某个位置所需要的代价大小跟我要去掉这个位置的数字个数以及上一个去掉的位置有关

所以我们考虑DP

当我们往DP方向考虑的时候这道题也就简单很多了,正如我所说的,去掉某个位置所需要的代价大小跟我去掉这个位置的数量个数以及上一个去掉的位置有关,那么我们完全可以反过来遍历,每次遍历都遍历上一个去掉的位置,递推关系也很明显,每次操作去掉当前位置的数字,然后会加上当前的mex值,注意最后一次除完当前位置的数组之后mex值有所改变

用cnt[i]表示某个数字出现次数,那么递推关系书就可以表示为dp[i] = dp[j] + (cnt[i] - 1 ) * j + i,注意有可能当前位置的数字是第一个去掉的数字,所以初始值记得是(cnt[i] - 1 ) * (刚开始的mex值)

其实还是挺典型的线性Dp的这道题,没明白的可以想一下最长递增子序列这道经典DP的O( n 2 n^2 n2)做法,你就会发现两者其实很类似,都是我所说的跟上一个选择的位置以及当前位置的状态有关

总结来讲还是挺好做的,个人认为没有C困难,C的数论可能没那么好想,但是这道DP还是很典型的DP的

代码如下:

#include<bits/stdc++.h>

using namespace std;

#define int long long
#define lowbit(x) x&(-x)

const int MOD=1e9+7;
const int N=1000100;

int a[N];
int b[N];
int dp[N];

void solve() {
    init();

    int n;
    map<int,int>mp;
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i],mp[a[i]]++;

    sort(a+1,a+n+1);
    n = unique(a+1,a+n+1) - a - 1;

    int zhi=0;
    for(int i=1;i<=n;i++) {
        if(a[i]!=zhi) break;
        zhi++;
    }

    n=zhi;
    //for(int i=1;i<=n;i++) cout<<a[i]<<" ";
    //cout<<"\n";
    if(n < 1) {
        cout<<0<<"\n";
        return ;
    }
    for(int i=n;i>=1;i--) {
        int minvalue = a[i] + (mp[a[i]] - 1) * (a[n] + 1);
        for(int j=n;j>i;j--) {
            minvalue = min(minvalue,dp[j] + a[i] + (mp[a[i]] - 1) * a[j] );
        }
        dp[i] = minvalue;
        //cout<<a[i]<<" "<<dp[i]<<"\n";
    }
    cout<<dp[1]<<"\n";
}

signed main() {
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);

    //for(int i=1;i<=cnt;i++) cout<<prime[i]<<"\n";

    int t=1;
    cin>>t;
    while(t--) {
        solve();
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值