Codeforces Round #642 (Div. 3)

Codeforces Round #642 (Div. 3)

D. Constructing the Array

题意: 一个全零序列,每次操作选择最长靠左的子段[l,r],将a[(l+r)/2]赋值,然后分成2个子段,求最终序列。

题解: 模拟。直接使用优先队列进行模拟。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const MAXN = 2e5 + 10;
int n, m, T, res[MAXN];
struct Node {
    int len, left;
    bool operator<(const Node &w) const{
        if (len != w.len) return len < w.len;
        else return left > w.left;
    }
};
priority_queue<Node> q;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> T;
    while(T--) {
        while(q.size()) q.pop();
        cin >> n;
        for (int i = 1; i <= n; ++i) res[i] = 0;
        q.push({n, 1});
        int idx = 0;
        while(q.size()) {
            auto t = q.top();
            q.pop();
            int l = t.left, r = l + t.len - 1;
            int mid = 0;
            if ((r - l + 1) & 1) mid = (l + r) / 2;
            else mid = (l + r - 1) / 2;
            res[mid] = ++idx;
            if (mid - l > 0) q.push({mid - l, l});
            if (r - mid > 0) q.push({r - mid, mid + 1});
        }
        for (int i = 1; i <= n; ++i) cout << res[i] << " ";
        cout << endl;
    }
    return 0;
}

E. K-periodic Garland

题意: 给一个长度为n的01字符串和一个整数k,每次操作可以将字符串中0变成1或者1变成0,代价是1。问最少多少次操作之后,可以将字符串中最近1的位置差恰好全部变成k。

题解: 状态机dp。对于每一位来说,只有2种状态,要不然为1,要不然为0,考虑状态机dp处理。

状态定义:

f [ i ] [ 0 ] f[i][0] f[i][0] : 前i个数据合法且第i个数据为0的最小改变次数

f [ i ] [ 1 ] f[i][1] f[i][1] : 前i个数据合法且第i个数据为1的最小改变次数

状态转移:

f [ i ] [ 0 ] = m i n ( f [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 1 ] ) + ( s [ i ] = = ′ 1 ′ ) ; f[i][0] = min(f[i - 1][0], f[i - 1][1]) + (s[i] == '1'); f[i][0]=min(f[i1][0],f[i1][1])+(s[i]==1);

f [ i ] [ 1 ] = m i n ( f [ i − k ] [ 1 ] + s u m [ i − 1 ] − s u m [ i − k ] , s u m [ i − 1 ] ) + ( s [ i ] = = ′ 0 ′ ) ; f[i][1] = min(f[i - k][1] + sum[i - 1] - sum[i - k], sum[i - 1]) + (s[i] == '0'); f[i][1]=min(f[ik][1]+sum[i1]sum[ik],sum[i1])+(s[i]==0);

s u m [ i ] sum[i] sum[i]维护到第i个时1的数目。

其中 f [ i ] [ 0 ] f[i][0] f[i][0]比较简单,不解释; f [ i ] [ 1 ] f[i][1] f[i][1]有2种选择,要不然从上一个1转移来,那么在 i − k ∼ i i - k \sim i iki这段内的1就必须改为0,因此需要修改 s u m [ i − 1 ] − s u m [ i − k ] sum[i - 1] - sum[i - k] sum[i1]sum[ik]次,要不然当前这个1就是第一个1,那么前面的1就必须全部改掉。

代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;
int const MAXN = 1e6 + 10;
int n, m, T, k, f[MAXN][2], sum[MAXN];
string s;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    cin >> T;
    while(T--) {
        cin >> n >> k;
        for (int i = 0; i <= n; ++i) f[i][0] = f[i][1] = 0;
        cin >> s;
        s = " " + s;
        for (int i = 1; i <= n; ++i) sum[i] = sum[i - 1] + (s[i] == '1');
        for (int i = 1; i <= n; ++i) {
            f[i][0] = min(f[i - 1][0], f[i - 1][1]) + (s[i] == '1');
            f[i][1] = min(f[max(i - k, 0)][1] + sum[i - 1] - sum[max(i - k, 0)], sum[i - 1]) + (s[i] == '0');
        }
        cout << min(f[n][0], f[n][1]) << endl;
    }
    return 0;
}

F. Decreasing Heights

题意: 给定一个n∗m的地图,每个格子有初始高度。只能向右或向下移动,且要求第i+1步的高度必须比第i步恰好高1。每次操作可以使得任意一个格子的高度减1。问最少需要几次操作,使得地图中存在由(1,1)到(n,m)的路径。

题解: 从(1, 1)到(i, j)一定存在一个点,这个点的高度不需要减一(因为大家如果都减一,那么等价于大家都不减一,所以至少有一个点不需要减一)。那么去枚举这个点为(x, y),则就能够推导出(1, 1)点的高度,然后由(1, 1)点的高度去推导出所以点的高度。所有枚举的(x, y)的操作次数取个min即可。

代码:

#include <bits/stdc++.h>

using namespace std;
#define int long long
const int INF = 1e18;
const int MAXN = 100 + 10;
int Map[MAXN][MAXN], dp[MAXN][MAXN];
int n, m;
int solve(int begin) {
    //对于每一个不同的起点值,都要初始化dp数组
    //注意从0开始初始化,否则更新dp[2][1]与dp[1][2]时会出错
    for (int i = 0; i <= n; i++) {
        for (int j = 0; j <= m; j++) {
            dp[i][j] = INF;
        }
    }
    dp[1][1] = Map[1][1] - begin;
    //起点格子的花销
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (i == 1 && j == 1) continue;
            // dp[1][1]算过了,跳过
            int nowh = begin + i + j - 2;
            //以标准高度为基准计算(i,j)格的应有高度
            if (Map[i][j] < nowh) continue;
            //实际高度比应有高度还小,没救了
            int cost = Map[i][j] - nowh;
            //计算(i,j)格的花费
            dp[i][j] = min(dp[i - 1][j] + cost, dp[i][j - 1] + cost);
        }
    }
    return dp[n][m];
}

signed main() {
    int t;
    cin >> t;
    while (t--) {
        int ans = INF;
        cin >> n >> m;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                cin >> Map[i][j];
            }
        }

        //尝试通过遍历所有可能的标准高度后计算得出起点值,再从这个起点值开始dp到终点
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                int begin = Map[i][j] - i - j + 2;
                if (begin <= Map[1][1]) ans = min(ans, solve(begin));
                //否则实际起点值比理论起点值还小,这个起点值是不可用的
            }
        }
        cout << ans << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值