Codeforces Round #726 (Div. 2)A-E1

A. Arithmetic Array

题意:给定一个长度为n的序列,每次操作可以添加一个非负数到序列的末尾,问最少需要几次操作使得序列变成一个好序列(好序列的定义是序列的平均值为1,注意是恰好为1)

题解

要使得序列的平均值为1,也就是要使得序列中所有值的和为n
设给定序列所有值的和为sum,如果sum < n,那么只需添加一个n - sum即可,答案为1;如果sum = n,那么不需要添加,答案为0;如果sum > n,那么根据贪心的思想,每次添加一个最小的非负数也就是0,最少需要添加sum - n个0,答案为sum - n

代码实现

#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#define ll long long
using namespace std;
int t, n, x;   
int main(){
    cin >> t;
    while(t--){
        cin >> n;
        int sum = 0;
        for(int i = 1; i <= n; i++){
            cin >> x;
            sum += x;
        }
        if(sum < n) cout << 1 << "\n";
        else cout << sum - n << "\n";
    }
    return 0;
}   

B. Bad Boy

题意:有一个n行m列的矩阵,初始时Anton位于位置(i, j),你可以在矩阵任意位置放置两个球,问放在哪两个位置能使Anton捡这两个球并回到初始位置所需步数最多,Anton每步只能去到相邻的上下左右格子

题解

如果在矩阵中选择两个位置,使它们之间的距离最大,那肯定选择对角的点,本题中我们也可以这样放置,因为你会发现不管Anton位于哪个位置,在对角放置两个球,Anton需要2 *(n - 1)+ 2 * (m - 1)步,这是所有可能中最大的值,可能也有其他情况为这个结果,但放置在对角一定是一种答案

代码实现

#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#define ll long long
using namespace std;
int t, n, m, i, j;
int main(){
    cin >> t;
    while(t--){
        cin >> n >> m >> i >> j;
        cout << 1 << " " << 1 << " " << n << " " << m << "\n";
    }
    return 0;
}

C. Challenging Cliffs

题意:给定一个长度为n的序列h,你需要找到这个序列的一种排列,使得h[1] - h[n]的绝对值最小,并且这个序列的困难值最大(对于i(1 <= i < n),如果h[i] <= h[i + 1],则困难值加1)

题解

由困难值的定义可知,要使困难值尽可能大,序列应当单调不减,所以我们可以先将序列进行从小到大的排序,又由于要满足序列的第一个值和最后一个值的差值最小,那么我们可以遍历排序后的序列中的相邻两项(排完序后差值最小的两个数一定是相邻的两个数),找到差值最小的两个数,记为h[k],h[k + 1],然后只需要将k + 1至n的数放在前面,然后将1至k的数放在后面,这样就能得到一个满足条件的序列
注意n = 2的时候特判一下,直接输出排完序后的h[1],h[2]即可

代码实现

#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#include <math.h>
#define ll long long
using namespace std;
const int N = 2e5 + 50;
int t, n;
int h[N];
int main(){
    cin >> t;
    while(t--){
        cin >> n;
        for(int i = 1; i <= n; i++) cin >> h[i];
        sort(h + 1, h + 1 + n);
        if(n == 2){
            cout << h[1] << " " << h[2] << "\n";
            continue;
        }
        int minn = 1e9, id = 0;
        for(int i = 1; i < n; i++){
            if(h[i + 1] - h[i] < minn){
                minn = h[i + 1] - h[i];
                id = i + 1;
            }
        }
        for(int i = id; i <= n; i++) cout << h[i] << " ";
        for(int i = 1; i < id; i++) cout << h[i] << " ";
        cout << "\n";
    }
    return 0;
}

D. Deleting Divisors

题意:Alice和Bob在玩一个游戏,初始有一个值n,每次操作可以选择n的一个因子(不包括1和n),然后令n减去这个因子,当某个人无法操作时,这个人就输了。(Alice先手)

题解

这是个博弈的问题,这种问题需要根据游戏要求确定一些必胜和必输状态,总而进行判断
本题是对于数字进行操作,那自然而然和数字的一些性质相关

首先先看n为奇数的情况,如果n是一个质数,那Alice直接无法操作,Bob赢;如果n是一个合数,那n一定能表示为两个奇数相乘的形式,无论Alice选择减去哪个奇数因子,Bob只需要和她减去同样的因子,那么又会变为两个奇数相乘的形式,所以这种情况Bob赢。

然后考虑n为偶数的情况,这种情况还需要细分为n是否为2的幂次,因为2比较特殊,既是偶数也是质数。

如果n不是2的幂次,那其实类似于奇数合数的情况,只不过这次Alice可以先手让n变成一个奇数合数,所以Alice赢。

如果n是2的幂次,即n = 2 k 2^k 2k,选择的因子只能是 2 i 2^i 2i(i < k),这样n就变成了 2 i 2^i 2i( 2 k − i 2^{k - i} 2ki - 1),如果i无法等于k/2(即k是个奇数),那就变成了上面那种情况,会导致操作完后进入必胜态,先手输,但如果i可以等于k/2(即k是个偶数),那么n会变成2的奇数次幂,进入了必输态,所以先手赢。

综上:当n为奇数时,Bob赢。
当n为偶数时:如果n不是2的幂次,Alice赢;如果n是2的幂次,当n为2的奇数次幂时,Bob赢,当n为2的偶数次幂时,Alice赢。

这里判断n是否是2的次幂时,用到了点位运算的知识,我们知道如果一个数n是2的次幂,那么它的二进制可以表示为10000…,那么n - 1的二进制可以表示为1111…,位数比n少1,那么n &(n - 1)就等于0,所以我们可以用n &(n - 1)的结果来判断n是否是2的次幂

代码实现

#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#define ll long long
using namespace std;
int t, n;
int main(){
    cin >> t;
    while(t--){
        cin >> n;
        if(n % 2 == 1) cout << "Bob" << "\n";
        else{
            if(n & (n - 1)) cout << "Alice" << "\n";
            else{
                int cnt = 0;
                while(n > 1){
                    cnt++;
                    n >>= 1;
                }
                if(cnt % 2 == 1) cout << "Bob" << "\n";
                else cout << "Alice" << "\n";
            }
        }
    }
    return 0;
}

E1. Erase and Extend (Easy Version)

题意:给定一个长度为n的字符串和一个数字k,每次操作可以选择删去字符串的最后一个字符或者复制当前字符串加到末尾,经过若干次操作可以得到一个长度为k且字典序最小的字符串,输出这个字符串。

题解

本题的数据范围较小,所以可以尝试点暴力的解法

先说思路,对于给定的字符串的每一个前缀,我们进行一种操作,即不断复制本身,当当前字符串长度大于k时,减去多余部分,答案就是所有这样操作后的字符串中字典序最小的那个

这样为什么正确呢,会不会出现复制的过程中进行几次删除操作会使结果更优呢?如果是这样的话,那么说明删除末尾某些字符后的字符串的字典序更小,也就是比当前前缀更短的前缀会更优,但是所有前缀的情况我们都计算过了,所以最终的答案一定是最优的。

代码实现

#include <algorithm>
#include <iostream>
#include <stdio.h>
#include <iomanip>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <set>
#include <map>
#define ll long long
using namespace std;
int n, k;
string s;
string solve(string t){
    while(t.size() < k) t += t;
    t = t.substr(0, k);
    return t;
}
int main(){
    cin >> n >> k >> s;
    string minn = "";
    for(int i = 1; i <= k; i++) minn += s[0];
    string tmp = "";
    tmp += s[0];
    for(int i = 1; i < n; i++){
        tmp += s[i];
        minn = min(minn, solve(tmp));
    }
    cout << minn;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值