Codeforces Round #601 (Div. 2) A-E

比赛链接

A

题意

给两个数字 \(a,b\) ,每次操作可以使 \(a\) 加上 \(+1,+2,+5,-1,-2,-5\) 中的一个数,求最少多少次操作可以将 \(a\) 变成 \(b\)

题解

知识点:贪心。

可以贪心取,先 \(5\)\(2\)\(1\)

一点小结论(可能是假的qwq):

考虑三个硬币 \(a>b>c\) ,令 \(a = kb+mc,b = nc\)

\(n - m \leq k\) 则任意数量的 \(a\) 都不可替代。

\(n - i(m-1)-1 \leq ik,i \geq 1\) ,则一次不能替代大于等于 \(i\)\(a\) ,但可以用 \(jk+1\)\(b\) 替代 \(j\)\(a\)\(n-jm\)\(c\) ,其中 \(j<i\)

时间复杂度 \(O(1)\)

空间复杂度 \(O(1)\)

代码

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

bool solve() {
    int a, b;
    cin >> a >> b;
    int ans = abs(b - a) / 5 + abs(b - a) % 5 / 2 + abs(b - a) % 5 % 2;
    cout << ans << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

B

题意

\(n\) 个冰箱, \(m\) 个锁链,每个冰箱的价值为 \(a_i\) 。一个锁链可以连接两个冰箱,这两个冰箱就会被这条锁链锁住,而设置这条锁链的花费是连接的两个冰箱的价值之和。

要求你设置锁链使得每个冰箱至少被两条锁链锁住,并且花费最小。

题解

知识点:贪心,构造。

显然为了使得每个冰箱至少有两条锁链连着,那花费至少是编号总和乘 \(2\) ,考虑能否达成这个最小值。

显然,使用环形锁链结构,即可达成这个最小值,即 \(1 \to 2,2\to3,\cdots,n-1\to n,n \to 1\) ,而且用的锁链最少,为 \(n\) 条。

因此,若 \(n=2\) 或者 \(m<n\) 的情况,分别是不能构成环或锁链不够,那么是无解的。

时间复杂度 \(O(n)\)

空间复杂度 \(O(1)\)

代码

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

bool solve() {
    int n, m;
    cin >> n >> m;
    int ans = 0;
    for (int i = 1;i <= n;i++) {
        int x;
        cin >> x;
        ans += x;
    }
    if (n == 2 || m < n) return false;
    cout << 2 * ans << '\n';
    for (int i = 1;i < n;i++) {
        cout << i << ' ' << i + 1 << '\n';
    }
    cout << n << ' ' << 1 << '\n';
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

C

题意

有一个长为 \(n\) 的排列 \(p\) ,生成一个长为 \(n-2\) 的三元组序列 \(q\) ,其中 \(q_i = (p_i,p_{i+1},p_{i+2})\)

现在给你被打乱的 \(q\) ,即每个三元组内部被打乱, \(q\) 的排序也被打乱,求符合 \(q\) 的一个 \(p\)

题解

知识点:构造。

注意到 \(p\) 生成的 \(q\) 中,数字出现的次数是有规律的。 \(p_1,p_n\) 恰好出现一次, \(p_2,p_{n-1}\) 恰好出现两次,其余元素都会恰好出现三次。通过这个性质我们就能初步断定出现一次的一定在首或尾,出现两次的一定在第二个或倒数第二个,其余元素不确定。

因此,我们先预处理所有数出现的次数,同时维护每个数与其他数是否在同一个元组的关系。之后,我们可以先取一个出现一次的数作为 \(p_1\) ,那么 \(p_1\) 所在元组就没用了,我们把与 \(p_1\) 在一个元组里的数的次数减一,此时出现新的出现次数为 \(1\) 的数,他就是紧接着的第二个数,以此类推取数即可。

但是有一个特例,最后 \(5\) 个数的时候呈现 \(1,2,3,2,1\) ,取走一个 \(1\) 的数以后呈现 \(1,2,2,1\) ,再取一次就变成 \(1,1,1\) ,出现了三个 \(1\) 没法判断了,因此,我们要在一开始确定 \(p_1\) 的时候,直接把 \(p_{n-1},p_{n}\) 都确定了,这样到最后三个不确定的数中已经有两个确定过了就可以直接判断了。于是,我们开一个数组 \(vis\) 记录数有没有被取过,一开始把 \(p_{n-1},p_n\) 取走就行。

时间复杂度 \(O(n)\)

空间复杂度 \(O(n)\)

代码

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

int cnt[100007];
vector<int> g[100007];
bool vis[100007];

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1;i <= n - 2;i++) {
        int u, v, w;
        cin >> u >> v >> w;
        cnt[u]++;
        cnt[v]++;
        cnt[w]++;
        g[u].push_back(v);
        g[v].push_back(u);
        g[u].push_back(w);
        g[w].push_back(u);
        g[v].push_back(w);
        g[w].push_back(v);
    }
    vector<int> st;
    for (int i = 1;i <= n;i++) {
        if (cnt[i] == 1) st.push_back(i);
    }
    int fst = st[0];
    int lst = st[1];
    vis[fst] = vis[lst] = 1;
    int lst2;
    for (auto v : g[lst]) {
        if (cnt[v] == 2) {
            vis[v] = 1;
            lst2 = v;
            break;
        }
    }
    for (int u = fst, w = 0;u;u = w) {
        w = 0;
        cout << u << ' ';
        for (auto v : g[u]) {
            if (vis[v]) continue;
            cnt[v]--;
            if (cnt[v] == 1) w = v;
        }
    }
    cout << lst2 << ' ' << lst << '\n';
    return 0;
}

D

题意

给一张 \(r \times c\) 的地图,地图上有米 R 和空地 .

现在有 \(k\) 只鸡,鸡的行走规则是只能从一格走到相邻的四格,地图上的格子鸡都能走。

现在让你给每只鸡分配 \(1\) 个区域,鸡只能在自己的区域里走,行走规则不变,要求每个鸡能吃到米的数量中的极差(最大值与最小值的差)最小。

题解

知识点:贪心,数学。

注意到,实际上我们可以给任意鸡分配任意的米数,只要我们走蛇形分配区域,就能保证区域一定是连通的。

设总米数为 \(sum\) ,考虑给每只鸡先分配 \(\left\lfloor \dfrac{sum}{k} \right\rfloor\) 个米,多出来的 \(sum \bmod k\) 个米一人一个分配完,这样极差最小。

时间复杂度 \(O(rc)\)

空间复杂度 \(O(rc)\)

代码

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

char dt[107][107];
bool solve() {
    int r, c, k;
    cin >> r >> c >> k;
    int sum = 0;
    for (int i = 1;i <= r;i++) {
        for (int j = 1;j <= c;j++) {
            cin >> dt[i][j];
            if (dt[i][j] == 'R') sum++;
        }
    }
    int avg = sum / k;
    int rst = sum % k;
    int pos = 0, cnt = 0;
    for (int i = 1;i <= r;i++) {
        if (i & 1) {
            for (int j = 1;j <= c;j++) {
                if (dt[i][j] == 'R') cnt++;
                if (cnt > avg + (rst > 0)) {
                    cnt = 1;
                    pos++;
                    if (rst > 0) rst--;
                }
                if (pos < 10) dt[i][j] = pos + '0';
                else if (10 <= pos && pos < 36) dt[i][j] = pos - 10 + 'A';
                else dt[i][j] = pos - 36 + 'a';
            }
        }
        else {
            for (int j = c;j >= 1;j--) {
                if (dt[i][j] == 'R') cnt++;
                if (cnt > avg + (rst > 0)) {
                    cnt = 1;
                    pos++;
                    if (rst > 0) rst--;
                }
                if (pos < 10) dt[i][j] = pos + '0';
                else if (10 <= pos && pos < 36) dt[i][j] = pos - 10 + 'A';
                else dt[i][j] = pos - 36 + 'a';
            }
        }
    }
    for (int i = 1;i <= r;i++) {
        for (int j = 1;j <= c;j++) {
            cout << dt[i][j];
        }
        cout << '\n';
    }
    return true;
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int t = 1;
    cin >> t;
    while (t--) {
        if (!solve()) cout << -1 << '\n';
    }
    return 0;
}

E

题意

\(n\) 个数 \(a_i\) ,每次操作可以任选一个数 \(a_i\)\(1\) 并在 \(a_{i-1},a_{i+1}\) 中选择一个加 \(1\) (如果存在的话),即 \(a_i\) 给它的一个邻居一个 \(1\)

要求使用最少的操作次数,使得存在一个 \(k>1\) 能整除所有数。

Easy版 \(n \in [1,10^5],a_i \in \{ 0,1\}\)

Hard版 \(n \in [1,10^6],a_i \in [1,10^6]\)

题解

知识点:分解质因数,贪心,枚举。

如果最后 \(k\) 能整除 \(a_i\) ,等价于 \(k\) 能整除 \(sum = \sum a_i\) ,而总和是不变的,因此我们可以直接通过 \(sum\) 的因子求出 \(k\) 的可行值来枚举。

显然,对于 \(10^{12}\) 的数,枚举他的全部因子的复杂度是不可行的。实际上,并不是所有 \(k\) 都需要枚举的,我们只需要枚举 \(sum\) 的质因子 \(k\) 即可,因为对于一个合数因子的答案,他的质因子的答案不会更坏。质因子最多 \(12\) 个,复杂度完全可以接受。所以,我们先处理出 \(sum\) 的质因子 \(pfactor\) 数组,随后遍历求出操作取最小值。

对于一个确定的 \(k\) ,我们希望得到修改 \(a_i\) 数组的最小操作次数。我们可以从左往右遍历,假设前 \(i-1\) 个都已经能被整除,那么 \(a_i\) 没有必要对 \(a_{i-1}\) 操作了, 只可能把 \(a_i \bmod k\)\(1\)\(a_{i+1}\) 或者让 \(a_{i+1}\) 给自己 \(k - a_i \bmod k\)\(1\) ,从中取最小值就是 \(a_i\) 需要的操作。

注意,前 \(i-1\) 能被 \(k\) 整除后,前 \(i-1\) 个数的操作最后都积累到 \(i\) 身上了,因此对于 \(a_i\) ,它已经变成 \((\sum_{j=1}^i a_i) \bmod k\) 。因此,令 \((\sum_{j=1}^i a_i) \bmod k = pre_i\) ,我们每次取最小值时,实际上是取 \(\min (pre_i,k-pre_i)\)

特判 \(sum = 1\) 的情况无解。

对于easy版:

我们可以直接枚举因子 \(k\) ,总数是 \(100\) 量级。

我们对一个 \(k\) 求最小操作时,可以直接从 \(1\)\(n\) 贪心地每 \(k\)\(1\) 分一个区域,其他分法不会更优,每个区域的操作最小值是 \(k\) 个数到第 \(\left\lceil \dfrac{k}{2}\right\rceil\) 个数的距离总和(因为 \(a_i\) 都是 \(1\) ,代价等价于距离)。

关于段内最小值的证明方法:

假设 \(k\) 个数从小到大排序好了,第 \(i\) 个数的位置为 \(pos_i\)

\(f(x)\) 为所有数到 \(x\) 的距离总和,我们发现 \(f(x)\) 是一个以 \(k\) 个数分段的”单谷“函数(这里的单谷可以由连续一段作为谷),每段的图像是一条线段,因此我们可以直接对整数点作差 \(f(pos_{i+1})-f(pos_i)\) 发现极值点。

最后得到,其中一个极值点在 \(i = \left\lceil \dfrac{k}{2}\right\rceil\) ,同时由第 \(i\) 个点为起始的段 \(\left[ \left\lceil \dfrac{k}{2}\right\rceil,\left\lfloor \dfrac{k}{2}\right\rfloor+1 \right]\) 内的任何一个数都是极小值点(包括段内的小数)。

时间复杂度 \(O(n+\sqrt{\sum a_i})\)

空间复杂度 \(O(n)\)

代码

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

int a[1000007];

void get_pfactor(ll n, vector<ll> &pfactor) {
    for (int i = 2;1LL * i * i <= n;i++) {
        if (n % i == 0) {
            pfactor.push_back(i);
            while (n % i == 0) n /= i;
        }
    }
    if (n > 1) pfactor.push_back(n);
}

int main() {
    std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int n;
    cin >> n;
    ll sum = 0;
    for (int i = 1;i <= n;i++) cin >> a[i], sum += a[i];
    if (sum == 1) {
        cout << -1 << '\n';
        return 0;
    }

    vector<ll> pfactor;
    get_pfactor(sum, pfactor);

    ll ans = 1e18;
    for (auto p : pfactor) {
        ll pre = 0, cnt = 0;
        for (int i = 1;i <= n;i++) {
            (pre += a[i]) %= p;
            cnt += min(pre, p - pre);
        }
        ans = min(ans, cnt);
    }
    cout << ans << '\n';
    return 0;
}
posted @ 2023-01-27 18:42  空白菌  阅读(39)  评论(0编辑  收藏  举报