Codeforces Round #767 (Div. 2)

A - Download More RAM

题目大意

给定n个内存条, 每个内存条可以增加\(b_i\)的内存, 但前提是你有足够的内存\(a_i\)去使用, 初始内存为k

思路

只要内存大于所要求的\(a_i\), 就可以使内存增加\(b_i\), 那么按照a排序贪心就好

代码

#include <iostream>
#include <algorithm>
 
using namespace std;
 
const int N = 110;
 
int n, k;
struct Node {
    int a, b;
}a[N];
 
int main() {
    int T;
    cin >> T;
    while (T --) {
        cin >> n >> k;
        for (int i = 1; i <= n; i ++ ) cin >> a[i].a ;
        for (int i = 1; i <= n; i ++) cin >> a[i].b;
        sort(a + 1, a + n + 1, [](Node a, Node b) {
            return a.a < b.a;
        });
 
        for (int i = 1; i <= n; i ++)
            if (a[i].a <= k) {
                k += a[i].b;
            }
        cout << k << endl;
    }
    return  0;
}

B - GCD Arrays

题目大意

给定一个去区间[l, r], 问经过k次操作之后, 能不能使所有数的最大公约数不为1
每次操作为删除两个数, 并将这两个数的乘积加入数组

思路

区间[l, r], 一串相连的数字, 那么他们的最大公约数除了1, 只能是2了, 又或者所有数合成了一个

  • 合成一个, 需要至少r - l + 1次, 这里有个特例就是左右端点相等, 那这两个端点就不能为1了
  • 最大公约数为2, 必须得相邻两个相互配对, 而且如果右端点为奇数的话, 那么右端点就必须和左边的那一组配对

代码

#include <iostream>
#include <algorithm>
 
using namespace std;
 
const int N = 110;
 
int n, k;
struct Node {
    int a, b;
}a[N];
 
int main() {
    int T;
    cin >> T;
    while (T --) {
        int a, b, c;
        cin >> a >> b >> c;
        if (a == b && a > 1) puts("YES");
        else if ((b - a + 1 + (b % 2)) / 2 <= c) puts("YES");
        else puts("NO");
    }
    return  0;
}

C - Meximum Array

题目大意

每次可以从头开始, 选取任意长度的原数组, 然后将这段序列的MEX加入新数组中, 问最后形成的新数组最大是多少

思路

首先原数组是一定要被遍历完的

  • 新数组最大, 一定是先将所有数可以凑出的最大的MEX加入
  • 然后从0到n, 一旦遇到断点, 就将断点加入答案
  • 如果是0缺失的话, 应该是加入剩下原数组长度的0加入答案

然后是怎么样遍历
我们可以枚举当前的MEX, 如果后面存在这个数, 那么MEX++, 如果不存在, 那么这个MEX就是断点
预处理每个数的位置, 利用数组下标i来判断是否是否枚举完毕

代码

#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
 
using namespace std;
 
const int N = 2e5 + 10;
 
int n;
int a[N], b[N];
vector<int> idx[N];
vector<int> ans;
 
int main() {
    int T;
    cin >> T;
    while (T --) {
        cin >> n;
        ans.clear();
        for (int i = 0; i <= n; i ++) {
            idx[i].clear();
            b[i] = 0;
        }
 
        for (int i = 1; i <= n; i ++) {
            cin >> a[i];
            idx[a[i]].push_back(i);
        }
 
            int cnt = 0, i = 1;
            while (i <= n) {                                                         // 当没有枚举到数组末尾的时候
                //cout << ans.size() << ' ' << cnt << ' ' << i << endl;
                if (b[cnt] < idx[cnt].size()) {                                      // 如果当前的MEX在数组后面还有
                    i = max(i, idx[cnt][b[cnt]]);                                    // 将数组下标后移
                    b[cnt ++] ++;
                    if (cnt > n) {
                        for (int j = 0; j < cnt; j ++)                                // 所有数组下标之前的数, 都不能再进行访问
                            while (b[j] < idx[j].size() && idx[j][b[j]] <= i) b[j] ++;
                        ans.push_back(cnt);
                        cnt = 0;
                    }
                }
                else {
                    if (cnt == 0) {                                                    // 如果缺失的是0
                        while (i <= n) {
                            ans.push_back(0);
                            i ++;
                        }
                        break;
                    }
                    else {                                                              // 不是0, 缺失一个断点, 重新进行遍历
                        ans.push_back(cnt);
                        for (int j = 0; j < cnt; j ++)
                            while (b[j] < idx[j].size() && idx[j][b[j]] <= i) b[j] ++;
                        cnt = 0;
                        if (i == n) break;                                              // 数组已经到末尾就退出, 没有到就++, 继续进行下一轮的遍历
                        else i ++;
                    }
                }
            }
 
            cout << ans.size() << endl;
            for (auto j : ans) cout << j << ' '; cout << endl;
 
    }
    return  0;
}

D - Peculiar Movie Preferences

题目大意

给定n个长度不超过3的字符串, 问是否可以选取任意多个字符串来构成一个回文字符串

思路

主要是有长度不超过3这个限制, 这样我们就可以进行暴力的枚举, 回文串的长度无非是1, 2, 3, 4, 5, 6, (如果有更长的回文串的话, 我们一定可以删去其中的一部分)

  • 长度为1的字符串, 一定就是回文串了
  • 长度为2的字符串, 要么本身是回文, 要么是存在一个字符串正好相反
  • 长度为3的字符串, 第一种还是本身是回文, 第二种是存在一个相反的字符串, 第三种是存在一个长度为2的字符串, 可以拼接到后面或者前面形成一个回文, 第四种拼一个长度1的, 都有长度为1的了, 干嘛还需要组合

代码

#include <iostream>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
 
#define x first
#define y second
 
using namespace std;
 
int n;
map<string, vector<int> > mp;
 
bool solve() {
    mp.clear();
 
    cin >> n;
    for (int i = 1; i <= n; i ++ ) {
        string x;
        cin >> x;
        mp[x].push_back(i);
    }
 
    for (auto i : mp) {
        if (i.first.size() == 1) {
            return true;
        }
        else if (i.first.size() == 2) {
            string x = i.first;
            reverse(x.begin(), x.end());
            if (mp.count(x)) return true;
        }
        else {
            string x = i.first.substr(0, 2); // 拼接到这个后面
            string y = i.first.substr(1, 2); // 拼接到这个前面
            string z = i.first;
            reverse(x.begin(), x.end());
            reverse(y.begin(), y.end());
            reverse(z.begin(), z.end());
 
            if (mp.count(x) && mp[x].back() > i.second.front()) return true;
            if (mp.count(y) && mp[y].front() < i.second.back()) return true;
            if (mp.count(z)) return true;
        }
    }
    return false;
}
 
int main() {
    int T;
    cin >> T;
    while (T --) {
        if (solve()) puts("YES");
        else puts("NO");
    }
}

E - Grid Xor

题目大意

给定一个n \(\cdot\) n的矩阵,矩阵中点(i,j)的值为其四周的格子的异或值组成,要我们求所有格子的的数的异或值。保证n是偶数

思路

我们通过模拟4 \(\cdot\) 4的发现一定是两个两个选的,且不能选会覆盖之前已经选了的区域。根据题目中可以证明解一定唯一,我们得知解一定存在,又由于是正方形,因此我们一行一行的选取格子并将其覆盖区域染色,时刻保证:

  • 前面的空格不被遗漏
  • 后面新选取的格子覆盖的区域与之前无重叠
    因为有重叠了等于没染,此时又要新的格子来染被消除的,会导致新的格子被消除。

代码

#include <iostream>
#include <cstring>
using namespace std;

const int N = 1010;

int n;
int a[N][N];
bool st[N][N];
int dx[] = { 0, 0, 1, -1 };
int dy[] = { 1, -1, 0, 0 };

int main() {
    int T;
    cin >> T;
    while (T --) {
        memset(st, 0, sizeof st);

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

        int ans = 0;
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= n; j ++ ) {
                bool f = false;
                for (int k = 0; k < 4; k ++) { //先判断四周有没有被覆盖到
                    int tx = i + dx[k], ty = j + dy[k];
                    if (st[tx][ty]) {
                        f = true;
                    }
                }

                if (!f) { //如果没有被覆盖到,则选择
                    ans ^= a[i][j];
                    for (int k = 0; k < 4; k ++) { //将四周覆盖
                        int tx = i + dx[k], ty = j + dy[k];
                        st[tx][ty] = true;
                    }
                    st[i][j] = true;
                }
            }
        
        cout << ans << endl;
    }
    return 0;
}

F - Game on Sum

题目大意

n轮游戏,每轮一个数,可以加上或减去,但至少加上的轮数要是m轮,每个数的范围为[0,k]的实数,问最终得分多少

思路

动态规划
f[i][j]表示当前进行了i轮, 进行过j次加法的最小得分
则f[i][j] = min(x + f[i - 1][j - 1], -x + f[i - 1][j]), x \(\in\) [0, k]
x要在[0, k]之间枚举还是要超时, 显然取中间值时是最优选择, 上式可以优化为f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) / 2
这里要注意就是n == m的情况.
\(\Downarrow\)
观察上面的状态转移方程:f[i][j] = (f[i − 1][j] + f[i − 1][j − 1]) / 2,需要求的是f[n][m],如果不考虑除以2,是不是很像路径方案数的状态转移方程?
的确是这样,与(1, 1)到(n, m)的方案数求法的一点区别不同就是初始值的不同,f[i][i] = i ∗ k(由F1可知),而从(i, j)到(n, m)的方案数为\(C\binom{n - 1}{m - 1}\)。我们考虑从(i, i)到(n, m)的贡献值即可,贡献值为\(C\binom{n - i - 1}{m - 1} \cdot f[i][i] \cdot \frac{1}{2^{n - i}}\)(因为中间状态转移时被除了n − i个2),将这些贡献值加起来即可
书的边角太小以至于写不下我的证明

代码1(动态规划, 只能完成F1)

#include <iostream>

using namespace std;
typedef long long LL;

const int N = 2010, mod = 1e9 + 7;

int n, m, k;
LL f[N][N];

LL qmi(LL a, LL b) {
    LL ans = 1;
    while (b) {
        if (b & 1) ans = (ans * a) % mod;
        b >>= 1;
        a = (a * a) % mod;
    }
    return ans;
}

int main() {
    int T;
    cin >> T;
    while (T --) {
        cin >> n >> m >> k;
        for (int i = 1; i <= n; i ++ ) {
            f[i][0] = 0;
            f[i][i] = k * 1ll * i % mod;
        }

        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++)
                if (i != j)
                    f[i][j] = (f[i - 1][j - 1] + f[i - 1][j]) * qmi(2, mod - 2) % mod;

        cout << f[n][m] << endl;
    }
    return 0;
}

代码2(数论)

#include <iostream>

using namespace std;
typedef long long LL;

const int N = 1e6 + 10, mod = 1e9 + 7, inv = 500000004; // inv = qmi(2, mod - 2)

int n, m, k;
LL fac[N], infac[N];

LL qmi(LL a, LL b) {
    LL ans = 1;
    while (b) {
        if (b & 1) ans = (ans * a) % mod;
        b >>= 1;
        a = (a * a) % mod;
    }
    return ans;
}

LL C(LL a, LL b) {
    return fac[a] * infac[b] % mod * infac[a - b] % mod;
}

int main() {
    fac[0] = infac[0] = 1;
    for (int i = 1; i < N; i ++ ) {
        fac[i] = fac[i - 1] * i % mod;
        infac[i] = qmi(fac[i], mod - 2);
    }

    int T;
    cin >> T;
    while (T --) {
        cin >> n >> m >> k;
        if (n == m) cout << n * 1ll * k % mod << endl;
        else {
            LL ans = 0;
            for (int i = 1; i <= m; i ++ )
                ans = (ans + i * 1ll * C(n - i - 1, m - i) % mod * qmi(inv, n - i)) % mod;
            cout << ans * k % mod << endl;
        }
    }
    return 0;
}

AK之后的颜色竟然是#D4EDC9

posted @ 2022-01-23 11:22  哇唔?  阅读(119)  评论(0编辑  收藏  举报