【解题报告】Codeforces Round #392 (Div. 2)

题目链接


A.Holiday Of Equality(Codeforces 758A)

思路

国王想要让每个人民的福利均等,且只能增加福利。那么代价最小的策略莫过于让所有人的福利都等于当前福利最大的人的福利(假设有更优的策略,那么定有人的福利减少了,与题意不符)。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 110;
int n, Max, ans, a[maxn];

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        Max = max(Max, a[i]);
    }
    for(int i = 1; i <= n; i++) {
        ans += (Max - a[i]);
    }
    printf("%d\n", ans);
    return 0;
}

B.Blown Garland(Codeforces 758B)

思路

这题的巧妙之处在于,当字符串的前四个字符确定以后,其它所有的字符也就确定了(可以通过样例观察出来)。因此只要确定了前四个字符,就能解决本题。相比构造,判定要更容易些。也就是说,如果前四个字符确定了,那么就可以判断这四个字符是否是合法的。因此我们枚举前四个字符,判断这四个字符是否合法,若合法则输出答案。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 110;
bool vis[maxn];
char s[maxn];
int n, a[maxn], b[5], ans[5];

// 在前四个字符合法的时候构造正确答案
void solve() {
    for(int i = 1; i <= n; i++) {
        int idx = (i - 1) % 4 + 1;
        if(a[i] > 0) {
            continue;
        }
        ans[b[idx]]++;
    }
}

// 判断枚举的前四个字符是否合法
// 根据b数组判断a数组是否合法
bool ok() {
    for(int i = 1; i <= n; i++) {
        int idx = (i - 1) % 4 + 1;
        if(a[i] != 0 && a[i] != b[idx]) {
            return false;
        }
    }
    return true;
}

// 用递归的方式枚举前四个字符
// 也就是填4个元素的b数组
// (当然也可以用四层循环)
void dfs(int cur) {
    if(cur > 4 && ok()) {
        solve();
    }
    for(int i = 1; i <= 4; i++) {
        if(vis[i] == true) {
            continue;
        }
        b[cur] = i;
        vis[i] = true;
        dfs(cur + 1);
        vis[i] = false;
    }
}

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%s", s + 1);
    n = strlen(s + 1);
    // 将字符编码有利于后面的枚举
    for(int i = 1; i <= n; i++) {
        if(s[i] == 'R') {
            a[i] = 1;
        } else if(s[i] == 'B') {
            a[i] = 2;
        } else if(s[i] == 'Y') {
            a[i] = 3;
        } else if(s[i] == 'G') {
            a[i] = 4;
        }
    }
    // 递归枚举
    dfs(1);
    // 输出答案
    for(int i = 1; i <= 4; i++) {
        printf("%d ", ans[i]);
    }
    puts("");
    return 0;
}

思路

实际上,当字符串前四个字符都确定后,字符串每四个字符也就确定了。也就是说设字符串的第 k 个字符确定后,字符串上满足 imod4=k 的第 i 个字符也就确定了(假设第 0 个字符为首字符)。
我们不妨将 imod4=0 的位置称作位置 0 ,以此类推还有位置 12 3 。在扫描字符串的同时我们可以确定某个字符对应着哪个位置,比如在下面的代码中 idx[R] ,表示 R 字符对应着的位置,还可以求出对于某个位置我们需要修改它多少次,例如下面代码中的 ans[0] ,表示位置 0 要被修改多少次。而 ans 就是我们要的答案,这样以来实现的难度也就大大降低了。

代码

#include <bits/stdc++.h>
using namespace std;

const int maxn = 110;
int idx[256], ans[4];
string s;

int main() {
    cin >> s;
    for(int i = 0; i < s.size(); i++) {
        if(s[i] != '!') {
            idx[s[i]] = i % 4;
        } else {
            ans[i%4]++;
        }
    }
    cout << ans[idx['R']] << ' ';
    cout << ans[idx['B']] << ' ';
    cout << ans[idx['Y']] << ' ';
    cout << ans[idx['G']] << ' ';
    return 0;
}

C.Unfair Poll(Codeforces 758C)

思路

老师点名是有周期性规律的。其周期 T=2m(n1) 。看到这个式子自然会想到当行是 1 的时候周期岂不是 0 吗?于是对于这种情况单独讨论,其周期 T=m
于是对于 kmodT 次点名,我们可以暴力处理。对于 k/T 次点名,由于可以算出每个位置在一个周期中被访问多少次,所以我们可以通过遍历位置的方式来处理。
实现上就是填写矩阵 a 。其中 a[i][j] 表示第 i 行第 j 列的位置别点名多少次。最后遍历矩阵就可得解。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 110;
int n, m, x, y;
ll k, T, Max, Min, G[maxn][maxn];

// 暴力填写矩阵
void brute(int k) {
    int x = 1, y = 0, dx;
    for(int i = 1; i <= k; i++) {
        if(y == m) {
            if(x == 1) {
                dx = 1;
            } else if(x == n) {
                dx = -1;
            }
            x += dx;
            y = 1;
        } else {
            y++;
        }
        G[x][y]++;
    }
}

int main() {
//  freopen("data.txt", "r", stdin);
    cin >> n >> m >> k >> x >> y;
    // 分类讨论,处理周期的整数倍点名
    if(n == 1) {
        T = m;
        for(int j = 1; j <= m; j++) {
            G[1][j] = k / T;
        }
    } else {
        T = 2 * m * n - 2 * m;
        for(int j = 1; j <= m; j++) {
            G[1][j] = G[n][j] = k / T;
        }
        for(int i = 2; i <= n - 1; i++) {
            for(int j = 1; j <= m; j++) {
                G[i][j] = k / T * 2;
            }
        }
    }
    // 暴力处理剩余的点名
    brute(k % T);
    Max = -1;
    Min = (1LL << 60);
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            Max = max(Max, G[i][j]);
            Min = min(Min, G[i][j]);
        }
    }
    cout << Max << ' ' << Min << ' ' << G[x][y];
    return 0;
}

D.Ability To Convert(Codeforces 758D)

思路

题目的意思是,将 k 划分成若干个部分,每部分表示 n 进制的一个位,问如何划分能使这个数的十进制表示最小。
本题有贪心策略:对 k 从尾部向头部处理,每次取尽可能多的数字,将其转化成十进制数。
为什么这样是正确的呢?假设在处理到某步的时候最多能取 p 个数字并将其转化成十进制数,但我们取 q 个数字(其中 q<p )。那么将会有 pq 个数字被分到更高位,假设当前处理到的权为 nx ,那么这些数字被分到更高位会使得当前十进制数最多减少 a=n(x+1) ,但是处理更高位时,当前十进制数最少都会增加 b=nx+1 。最后答案增加的值 ba>0
于是用不符合上述贪心策略的策略的话,肯定没有使用上述贪心策略更好。所以上述的贪心策略是最好的。
在实现上,我这里用的是双指针法(左开右闭区间),也有称其为“尺取法”的。双指针指向的区间表示当前十进制位对应的n进制数的区间。

代码

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
int cnt, baseLen, numLen, head, tail;
ll nBase, nSub, ans, pro;
string sBase, sNum;

// 得到区间[l, r]内的子串
string getSub(int l, int r) {
    int len = r - l + 1;
    return sNum.substr(l, len);
}

// 将字符串转化成数字
ll getNum(string s) {
    ll res;
    stringstream ss(s);
    ss >> res;
    return res;
}

int main() {
    cin >> sBase >> sNum;
    baseLen = sBase.size();
    numLen = sNum.size();
    nBase = getNum(sBase);
    sNum = " " + sNum;
    pro = 1;
    // 双指针法,双指针指向区间(head, tail]
    for(tail = numLen; tail > 0;) {
        // 直接取n同样长度的k的子串
        head = max(tail - baseLen, 0);
        nSub = getNum(getSub(head + 1, tail));
        // 当子串大于或等于n时不合法,令子串减少一位
        if(head + 1 < tail && nSub >= nBase) {
            head++;
        }
        // 处理掉子串中的前缀零
        while(head + 1 < tail && sNum[head+1] == '0') {
            head++;
        }
        ans += getNum(getSub(head + 1, tail)) * pro;
        // 累乘表示位权的变量
        pro *= nBase;
        tail = head;
    }
    cout << ans << endl;
    return 0;
}

(其它题目略)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值