Codeforces Round #642 (Div. 3) 做题记录

好菜啊,F变量名写错了没能AK,哭了。

A:Most Unstable Array

题意:给一个n和m,构造出一个长度为n的非负序列a,a中元素之和为m,要求a相邻两项差的和最大,输出这个最大值。

数据范围:1 <= n, m <= 1e9

题解:显然是直接放一个m就行了,判断一下长度就可以了。

代码:

#include <bits/stdc++.h>

using namespace std;

int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        if(n == 1) m = 0;
        else if(n > 2) m <<= 1;
        printf("%d\n", m);
    }
    return 0;
}

B:Two Arrays And Swaps

题意:给两个长度为n的序列a和b,每次操作可以交换a和b中的一个元素(任意交换),问至多进行k次操作,序列a的元素之和最大是多少。

数据范围:1 <= n <= 30,1 <= ai, bi <= 30, 0 <= k <= n

题解:直接对a,b进行排序,将b中大的和a中小的换即可。

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 100 + 7;
int a[maxn], b[maxn];
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        int n, k;
        scanf("%d%d", &n, &k);
        int ans = 0;
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &a[i]);
            ans += a[i];
        }
        for(int i = 1; i <= n; ++i) {
            scanf("%d", &b[i]);
        }
        sort(a + 1, a + n + 1);
        sort(b + 1, b + n + 1);
        int r = n;
        for(int i = 1; i <= n; ++i) {
            if(k == 0) break;
            if(b[r] > a[i]) {
                ans += b[r] - a[i];
                --k;
                --r;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

C:Board Moves

题意:给一个n * n的矩阵(n为奇数),矩阵中每个位置有一个人,每次操作可以使一个人向8个方向移动一格,问将所有人移动到同一格的最少操作数。

数据范围:1 <= n <= 5e5

题解:显示将所有人移到最中间操作数最少,8方向移动两个位置最短距离即max(|x1 - x2|, |y1 - y2|),假设矩阵中点位置为(mid, mid),则所求答案为:

\sum _{i = 1} ^ n \sum_{j = 1} ^ n |max(i, j) - mid|

发现有绝对值不好算,将矩阵分成四块,四块的答案显然是一样的,算其中一块即可。具体计算很简单。

小插曲:比赛时,直接算了没加绝对值的式子,算出了发现答案少了一倍,于是*2就交了上去,比赛下来以为要FST,仔细想了想,发现是对的。当时真莽啊。

发现这东西其实可以O(1)计算,不过没什么必要。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;

int main() {
    int t, n;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        int mid = (n + 1) / 2;
        ll ans = - 1LL * n * n * mid;
        ans += 1LL * n * n * (1 + n) / 2;
//        for(int i = 1; i <= n; ++i) {
//            ans += 1LL * (n - i) * i;
//        }
        --n;
        ans += 1LL * (n + 1) * n * (n + 1) / 2;
        ans -= 1LL * n * (n + 1) * (2 * n + 1) / 6;
        printf("%I64d\n", ans << 1);
    }
    return 0;
}

D:Constructing the Array

题意:给一个正整数n,让你构造一个长度为n的序列a,构造方法如下:

初始a中元素全为0。

每次操作,选最长的全0子段,如果有多个选最左边的那个,设这个区间为[l, r],如果区间长度为奇数,则使a[(l + r) / 2] = i,

否则使a[(l + r - 1) / 2] = i。其中i表示当前是第i次操作。

数据范围:1 <= n <= 2e5

题解:将区间放入优先队列,按照题目要求排序,每次取堆顶赋值, 再分成两半,直到没有合法区间。

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 2e5 + 7;

int a[maxn], n;

struct node{
    int l, r;
    bool operator < (const node &oth) const {
        int len1 = r - l + 1, len2 = oth.r - oth.l + 1;
        if(len1 == len2) return l > oth.l;
        return len1 < len2;
    }
};
priority_queue<node> pq;

int main(){
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        pq.push(node{1, n});
        int idx = 1;
        while(!pq.empty()) {
            node p = pq.top(); pq.pop();
            int mid = (p.l + p.r) / 2;
            a[mid] = idx;
            ++idx;
            if(mid - 1 >= p.l) pq.push(node{p.l, mid - 1});
            if(mid + 1 <= p.r) pq.push(node{mid + 1, p.r});
        }
        for(int i = 1; i <= n; ++i) printf("%d%c", a[i], i == n ? '\n' : ' ');
    }
    return 0;
}

E:K-periodic Garland

题意:给一个长度为n的01串。

定义一次操作为选一个位置将此位置的值反转。

定义k-periodic串:串中相邻1之间有k-1个0。

问最少几次操作可以将给定串变成k-periodic串。

数据范围:1 <= n <= 1e6, 1 <= k <= n

题解:由题意可知,串中模k同余的位置互相影响,并且与其他位置互不影响,我们将模k同余的位置取出来分别做,这样问题转化为给一个串,操作还是题目中的操作,问最少几次操作能使串中的1都连续。这个问题可以通过dp解决,枚举当前端点为1的最后端点,显然,要么前面全变成0,要么当前位的前一位变成1,对这两个取最小值即可。后面全变成0的代价可以预处理一下。

代码:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1e6 + 7;
char s[maxn];
int n, k;

string sk[maxn];
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &n, &k);
        scanf("%s", s);
        for(int i = 0; i < k; ++i) sk[i].clear();
        int one = 0;
        for(int i = 0; i < n; ++i) {
            sk[i % k] += s[i];
            if(s[i] == '1') ++one;
        }
        int ans = n + 1;
        for(int i = 0; i < k; ++i) {
            int tans = one;
            int tone = 0;
            for(char c : sk[i]) if(c == '1') ++tone;
            tans -= tone;
            int ttone = 0;
            int res = n + 1, dp = n + 1;
            for(char c : sk[i]) {
                if(c == '1') {
                    dp = min(dp, ttone);
                    ++ttone;
                }
                else {
                    dp = min(dp + 1, ttone);
                }
                res = min(res, dp + tone - ttone);
                //printf("i -- %d res -- %d dp -- %d\n", i, res, dp);
            }
            //printf("i -- %d tans -- %d\n", i, tans);
            tans += res;
            ans = min(ans, tans);
        }
        printf("%d\n", ans);
    }
    return 0;
}

F:Decreasing Heights

题意:给一个n*m的矩阵a,要从(1, 1)走到(n, m),每次只能向下(i => i + 1)或向右(j => j + 1)走,要求路径上相邻点的权值差1且递增。现在有一种操作,一次操作可以选出矩阵中的一个位置(i, j)使a[i][j] -= 1。问最少几次操作可以使(1, 1)能够走到(n, m)。

数据范围:1 <= n, m <= 100, 1 <= aij <= 1e15

题解:数据范围比较小,我们可以枚举路径上的经过点(x, y),使其值不变,其他值跟随其变化,做两个dp,一个从(x, y) 到(n, m)的dp和一个从(x, y)到(1, 1)的dp。枚举后取最小值。

代码:

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn = 100 + 7;
const ll inf = 1e18;
ll a[maxn][maxn], dp[maxn][maxn], dp2[maxn][maxn];
int n, m;


ll solve(int x, int y) {
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) dp[i][j] = dp2[i][j] = inf;
    }
    dp[x][y] = 0;
    for(int i = x; i <= n; ++i) {
        for(int j = y; j <= m; ++j) {
            if(i == x && j == y) continue;
            ll nowval = a[x][y] + i - x + j - y;
            if(nowval > a[i][j]) continue;
            ll cost = a[i][j] - nowval;
            if(i - 1 >= x) {
                dp[i][j] = min(dp[i][j], dp[i - 1][j] + cost);
            }
            if(j - 1 >= y) {
                dp[i][j] = min(dp[i][j], dp[i][j - 1] + cost);
            }
        }
    }
    dp2[x][y] = 0;
    for(int i = x; i >= 1; --i) {
        for(int j = y; j >= 1; --j) {
            if(i == x && j == y) continue;
            ll nowval = a[x][y] - (x - i + y - j);
            if(nowval > a[i][j]) continue;
            ll cost = a[i][j] - nowval;
            if(i + 1 <= x) {
                dp2[i][j] = min(dp2[i][j], dp2[i + 1][j] + cost);
            }
            if(j + 1 <= y) {
                dp2[i][j] = min(dp2[i][j], dp2[i][j + 1] + cost);
            }
        }
    }
    return dp[n][m] + dp2[1][1];
}
int main() {
    int t;
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                scanf("%I64d", &a[i][j]);
            }
        }
        ll ans = inf;
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= m; ++j) {
                ans = min(ans, solve(i, j));
            }
        }
        printf("%I64d\n", ans);
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值