2024 暑假友谊赛-热身1

2024 暑假友谊赛-热身1

A - 🐓

AtCoder - abc079_d

题意

给出每个点的花费,需要将它转换为 1,求最小花费。

思路

要想把所有数变成 1,那有两种选择,一是直接变成 1,二是将这个数先变成其他某个数,再有那个数继续迭代下去。

到这里,我们应该感觉到了,这与 𝑓𝑙𝑜𝑦𝑑 求最短路的过程一致,所以我们可以跑 𝑓𝑙𝑜𝑦𝑑 求解。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

const int N = 2e2 + 10;
int c[N][N], A[N][N];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int h, w;
    cin >> h >> w;

    for (int i = 0; i < 10; i ++)
        for (int j = 0; j < 10; j ++)
            cin >> c[i][j];

    for (int i = 1; i <= h; i ++)
        for (int j = 1; j <= w; j ++)
            cin >> A[i][j];

    for (int k = 0; k < 10; k ++)
        for (int i = 0; i < 10; i ++)
            for (int j = 0; j < 10; j ++)
                c[i][j] = min(c[i][j], c[i][k] + c[k][j]);

    i64 ans = 0;
    for (int i = 1; i <= h; i ++)
        for (int j = 1; j <= w; j ++)
            ans += c[A[i][j]][1];

    cout << ans << '\n';

    return 0;
}

B - 🐓🐓

AtCoder - arc100_a

题意

给 n 个数,你可以找任意数 b ,使得 \(|A_1-(b+1)|+\dots+|A_i-(b+i)|+\dots+|A_i-(b+n)|\) 最小。

思路

先写出通项: \(∣A_i−(b+i)∣\)

上式可以转化成:$ ∣(𝐴_𝑖−𝑖)−𝑏∣$

想要这个式子的值小,当然要让 𝑏 尽可能接近 \(𝐴_𝑖−𝑖\) ,所以我们预处理出 \(A_i−i\) 然后排序, b 取中位数时可以使得原式最小,也就是 \(A_{\lfloor\frac{n}{2}\rfloor+1}\)

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    i64 ans = 0;
    vector<i64> a(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        a[i] -= i;
    }

    sort(a.begin() + 1, a.end());

    for (int i = 1; i <= n; i ++) {
        ans += abs(a[i] - a[n / 2 + 1]);
    }

    cout << ans << '\n';


    return 0;
}

C - 🐓🐓🐓

AtCoder - arc099_a

题意

给一个排列,每次可以推平长度为 k 的区间使其都变为这个区间的最小值,求整个排列相等最少需要推平的次数。

思路

首先无论 𝑘 为多少,只要序列中有 1,那么平推后的结果必然全是 1。

所以只要先平推有 1 的 𝑘 个数区间,剩下还有 𝑛−𝑘 个数。每一次包含一个 1,再包含 𝑘−1 个其他数。这样,那 𝑘−1 个其他数都会变成 1,这个的次数为 \(\lceil\frac{n-k}{k-1}\rceil\),再加上 1,就等于 \(\lceil\frac{n-k+(k-1)}{k-1}\rceil=\lceil\frac{n-1}{k-1}\rceil\)

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    cin >> n >> k;

    int pos;
    vector<i64> a(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
    }

    int ans = (n - 1 + (k - 2)) / (k - 1);

    cout << ans << '\n';

    return 0;
}

D - 🐓🐓🐓🐓

AtCoder - arc100_b

题意

将一个序列 A 划分成连续的四段,使得这四段的和的极差最小。

思路

先求前缀和,于是题目转化成从 1∼𝑛−1 中选出三个数 𝑖,𝑗,𝑘 使得 \(𝑠_𝑖,𝑠_𝑗−𝑠_𝑖,𝑠_𝑘−𝑠_𝑗,𝑠_𝑛−𝑠_𝑘\) 的极差最小.

考虑枚举 𝑗 ,那么 𝑖,𝑘 一定是把 1∼𝑗−1 和 𝑗+1∼𝑛 划分得最平衡的分界点, 即使得 \(∣(𝑠_𝑗−𝑠_𝑖)−𝑠_𝑖∣,∣(𝑠_𝑛−𝑠_𝑘)−(𝑠_𝑘−𝑠_𝑗)∣\) 最小的 𝑖,𝑘 。

因为如果不平衡,那么划分出的两段的和的最小值值一定比平衡状态下的最小值更小,最大值比平衡状态大,从而总体的极差可能会变大。

由于 𝑗 是从小到大枚举的,从而 \(𝑠_𝑗−𝑠_𝑖\) 变大、 \(𝑠_𝑘−𝑠_𝑗\) 变小,所以最优化划分点 𝑖,𝑘 一定是单调不降的。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<i64> a(n + 1), pre(n + 1);
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        pre[i] = pre[i - 1] + a[i];
    }

    i64 ans = 1 << 30;

    i64 ma, mi, t1=1, t2=3;
    for (int i = 2; i < n; i ++) {
        while(t1 < i && abs(pre[t1]-(pre[i]-pre[t1]))>abs(pre[t1+1]-(pre[i]-pre[t1+1])))
            t1 ++;
        while(t2<n &&abs((pre[t2]-pre[i])-(pre[n]-pre[t2]))>abs((pre[t2+1]-pre[i])-(pre[n]-pre[t2+1])))
            t2 ++;
        ma = max({pre[t1], pre[i] - pre[t1], pre[t2] - pre[i], pre[n] - pre[t2]});
        mi = min({pre[t1], pre[i] - pre[t1], pre[t2] - pre[i], pre[n] - pre[t2]});
        ans = min(ans, ma - mi);

    }

    cout << ans << '\n';

    return 0;
}

E - 🐓🐓🐓🐓🐓

CodeForces - 1808C

题意

给你一个区间,你需要找出该区间任意一个数,这个数需满足其数位上最大值减去最小值的值最小。

思路

貌似不用数位 dp 也可以做,不过为了练习下我还是坚持用数位 dp。

考虑数位 dp。

当 l 和 r 位数不同时,一定可以找出全为 9 的数。

相同时考虑数位 dp,设计 \(dp_{Len,Max,Min,Up,Down}\),分别代表长度,最大值,最小值,上界,下界。

递归中用了一个 of 参数,是用来计算具体的数的,题目要求输出具体方案,所以用了另外一个数组存储。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

i64 dp[25][20][20][2][2];
i64 num[25][20][20][2][2];

void solve() {

    i64 l, r;
    cin >> l >> r;

    vector<int> U(20, -1), D(20, -1);
    D[1] = l % 10;
    i64 lenl = 1, offset = 1, tmp = l / 10, lenr = 1;
    while (tmp) {
        offset *= 10;
        D[++lenl] = tmp % 10;
        tmp /= 10;
    }

    tmp = r / 10, U[1] = r % 10;
    while (tmp) {
        U[++lenr] = tmp % 10;
        tmp /= 10;
    }

    if (lenl != lenr) {
        cout << string(lenr - 1, '9') << '\n';
        return ;
    }

    reverse(D.begin() + 1, D.begin() + lenl + 1);
    reverse(U.begin() + 1, U.begin() + lenr + 1);
    memset(dp, -1, sizeof dp);
    memset(num,0,sizeof num);

    auto dfs = [&](auto & self, int len, i64 of, int Max, int Min, bool up, bool down)->i64 {
        if (len == lenr + 1) return Max - Min;
        if (~dp[len][Max][Min][up][down])
            return dp[len][Max][Min][up][down];

        i64 Mans = 10;
        for (int i = (down ? D[len] : 0); i <= (up ? U[len] : 9); i ++) {
            i64 res = self(self, len + 1, of / 10, max(Max, i), min(Min, i), up & (i == U[len]), down & (i == D[len]));
            i64 val = num[len + 1][max(Max, i)][min(Min, i)][up & (i == U[len])][down & (i == D[len])];
            if (res < Mans) {
                Mans = res;
                num[len][Max][Min][up][down] = i * of + val;
            }
        }

        return dp[len][Max][Min][up][down] = Mans;
    };

    dfs(dfs, 1, offset, 0, 10, 1, 1);

    cout << num[1][0][10][1][1] << '\n';

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--)
        solve();

    return 0;
}

F - 🐓🐓🐓🐓🐓🐓

CodeForces - 1547E

题意

有 n 个格子,k 个空调,k 个空调所在的位置有初始温度,空调会以公差为 1 的温度影响两边的格子,同个格子被多个温度影响取最小值,问你所有格子的温度。

思路

记录一个温度指针,从左往右和从右往左分别扫一次,每次更新最小温度,并且每经过一个格子增加 1 度。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

    int n, k;
    cin >> n >> k;

    vector<int> ans(n + 1, 1 << 30), air(k + 1), t(k + 1);
    for (int i = 1; i <= k; i ++)
        cin >> air[i];
    for (int i = 1; i <= k; i ++) {
        cin >> t[i];
        ans[air[i]] = t[i];
    }

    int now = 1 << 30;
    for (int i = 1; i <= n; i ++) {
        now = min(++now, ans[i]);
        ans[i] = min(now, ans[i]);        
    }
    
    now = 1 << 30;
    for (int i = n; i >= 1; i --) {
        now = min(++now, ans[i]);
        ans[i] = min(now, ans[i]);
    }

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

}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--)
        solve();

    return 0;
}

G - 🐓🐓🐓🐓🐓🐓🐓

CodeForces - 1107C

题意

给你一个字母按钮的出招顺序,每个按钮对应一个 \(A_i\) 伤害,但对于同一个按钮不能点击超过 k 次,问你可以跳过一些按钮顺序使得造成的最大伤害为多少。

思路

枚举按钮顺序,对于同一个按钮,可以先存进堆里,碰到不同按钮或者到最后了,从堆里取出 k 个并清空堆即可。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, k;
    cin >> n >> k;

    vector<i64> a(n);
    for (int i = 0; i < n; i ++) {
        cin >> a[i];
    }

    string s;
    cin >> s;

    priority_queue<i64> Q;

    i64 ans = 0;
    for (int i = 0; i < n; i ++) {
        Q.push(a[i]);
        if (i == n - 1 || i < n - 1 && s[i] != s[i + 1]) {
            int cnt = 0;
            while (cnt++ < k && Q.size()) {
                ans += Q.top();
                Q.pop();
            }
            while (Q.size()) Q.pop();
        }
    }

    cout << ans << '\n';

    return 0;
}

H - 🐓🐓🐓🐓🐓🐓🐓🐓

AtCoder - arc102_b

题意

给你一个数 L,要求你构造一个 n 个点的有向图,且从 1 到 n 恰好有 L 条权值为 0 到 L-1 的路径。

思路

将 L 拆分成二进制,取最高位为 k ,从 i=1 到 k 每次构造 \(i\rightarrow i+1\) 权值为 0 和 \(2^{i-1}\) 的两条边,这样就能凑出 \([0,2^k-1]\) 的所有路径,如果 L 二进制中除了最高位还有其他位有 1 ,则从 i 连一条到 n 号点的路径,权值为抹掉这一位及以下所有数的值后的值,例如 L = 25 时,25 = 16 + 8 + 1,其二进制为 11001,构造完 1 到 5号点的边后只能凑出 \([0,2^4-1]\) 的,对于第 0 位的 1 ,还需要构造一条 1 到 5 权值为 \(24_{(11000)}\) 的边,对于第 3 位的 1 ,还需要构造一条 4 到 5 权值为 \(16_{(10000)}\) 的边。

代码

#include<bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int k = 0, L;
    cin >> L;

    while ((1 << (k + 1)) <= L)
        k ++;

    int n = k + 1;
    cout << n << ' ' << 2 * k + __builtin_popcount(L) - 1 << '\n';

    for (int i = 1; i <= k; i ++) {
        cout << i << ' ' << i + 1 << " 0" << '\n';
        cout << i << ' ' << i + 1 << ' ' << (1 << (i - 1)) << '\n';
    }

    for (int i = 0; i < k; i ++) {
        if ((L >> i) & 1) {
            cout << i + 1 << ' ' << n << ' ' << (L & ~((1 << (i + 1)) - 1)) << '\n';
        }
    }

    return 0;
}
posted @ 2024-07-12 19:54  Ke_scholar  阅读(7)  评论(0编辑  收藏  举报