【解题报告】Codeforces Round #371 (Div. 2)

题目链接


A. Meeting of Old Friends(Codeforces 714A)

思路

首先我们可以讨论两个区间的位置关系(内含,相交,相等或相离),相离情况下答案为 0 ,不相离的情况下求出这两个区间的公共部分,然后判断那个间断时间点在不在公共部分就能求出结果了。
比较巧妙的办法是,令 [L,R]=[max(l1,l2),min(r1,r2)] ,这实际上就是两个区间的公共部分(若这个区间的右端点在左端点的左边,就表示题给两个区间是相离的)。然后判断 k 是否在 [L,R] 中,如果在则答案为 RL ,否则为 RL+1
(以下的代码用的是直接分类讨论的方法)

代码

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

long long l1, r1, l2, r2, k, L, R;

int main() {
    cin >> l1 >> r1 >> l2 >> r2 >> k;、
    // 区间相离
    if(r1 < l2 || r2 < l1) {
        cout << 0;
    }
    // 区间相等或区间2包含区间1
    else if(l1 >= l2 && r1 <= r2) {
        cout << r1 - l1 + (l1 > k || k > r1);
    }
    // 区间相交
    else if(l2 > l1 && r2 < r1) {
        cout << r2 - l2 + (l2 > k || k > r2);
    }
    // 区间相交
    else {
        L = max(l1, l2);
        R = min(r1, r2);
        cout << R - L + (L > k || k > R);
    }
    return 0;
}

B. Filya and Homework(Codeforces 714B)

思路

我们先统计数组中不同数的个数是多少(假设它为 m )。然后可以根据 m 分类讨论:

  • m=1 m=2 。此时一定能让所有的数相等。
  • m=3 。若最大的数减第二大的数等于第二大的数减去最小的数则能够让所有数相等,否则不能。
  • m4 。此时一定无法让所有的数相等。

代码

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

const int maxn = 1e5 + 10;
int n, m, a[maxn];

int main() {
    scanf("%d", &n);
    for(int i = 0; i < n; i++) {
        scanf("%d", &a[i]);
    }
    sort(a, a + n);
    m = unique(a, a + n) - a;
    if(m >= 4) {
        puts("NO");
    }
    else if(m == 1 || m == 2) {
        puts("YES");
    }
    else {
        puts(a[2] - a[1] == a[1] - a[0] ? "YES" : "NO");
    }
    return 0;
}

C.Sonya and Queries(Codeforces 714C)

思路

首先,我们可以将输入的数字当成 01 字符串,其中奇数对应的位为 1 ,否则为 0 。如果不满 18 位则在 01 字符串的开头补 0 使其满 18 位。
然后,每读入一个操作符为 + 的操作数,就将其按照前面讲的方式变成 01 字符串并插入 Trie 中,将该字符串的权值根据操作符的不同加 1(+) 或减 1()
当操作符为 时,在 Trie 数中查找代表操作数的字符串,其权值为要输出的值。权值为 0 或字符串不存在都输出 0
(赛后发现有更简单的方法,就是直接将 01 字符串压缩成整数 k ,用 num[k] 表示 k 对应的数在多重集中有几个)

代码

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

const int maxNode = 2e6, sigmaSize = 5;

// Trie的模板
struct Trie {
    int ch[maxNode][sigmaSize];
    int val[maxNode];
    int sz;
    Trie() {
        sz = 1;
        memset(ch[0], 0, sizeof(ch[0]));
    }
    int idx(char c) {
        return c - '0';
    }
    void insert(string s, int v) {
        int u = 0, n = s.size();
        for(int i = 0; i < n; i++) {
            int c = idx(s[i]);
            if(!ch[u][c]) {
                memset(ch[sz], 0, sizeof(ch[sz]));
                val[sz] = 0;
                ch[u][c] = sz++;
            }
            u = ch[u][c];
        }
        val[u] += v;
    }
    int find(string s) {
        int u = 0, n = s.size();
        for(int i = 0; i < n; i++) {
            int c = idx(s[i]);
            if(!ch[u][c]) {
                return 0;
            }
            u = ch[u][c];
        }
        return val[u];
    }
}o;

int n;
string s, t, opt;

int main() {
    ios_base::sync_with_stdio(false);
    cin >> n;
    for(int i = 1; i <= n; i++) {
        cin >> opt >> s;
        // 处理成0-1字符串
        for(int i = 0; i < s.size(); i++) {
            s[i] = (s[i] - '0') % 2 ? '1' : '0';
        }
        string t;
        // 在首部添0
        for(int i = 1; i <= 18 - s.size(); i++) {
            t.push_back('0');
        }
        s = t + s;
        if(opt != "?") {
            o.insert(s, opt == "+" ? 1 : -1);
        }
        else {
            cout << o.find(s) << endl;
        }
    }
    return 0;
}

E. Sonya and Problem Wihtout a Legend(Codeforces 714E)

思路

先来思考一个简单的问题。当题目中的“严格单调递增”改成“单调非递减”时,问题该怎样解决。
用搜索解决肯定是复杂度太高了,如果用动态规划呢?我们先尝试划分阶段。假设我们定义阶段i为考虑到第 i 个数字为止的数组的前缀。那么理论上如果我们知道第 i1 个阶段下,第 i1 个数字被修改为x的最小修改次数,就能知道第 i 个阶段下,第 i 个数字被修改为 y(xy) 的最小修改次数。
于是我们定义 d[i][j] 为当考虑到第 i 个数字且第 i 个数字被修改为j时的最小修改次数。那么有如下状态转移方程成立

d[i][j]=min{d[i1][k]+a[i]j,jk}

现在似乎按照方程递推就能得解了。但是此处还有两个问题需要解决。第一, O(n3) 的复杂度还是太高了。这个问题可以通过建立前缀数组来解决,具体就是令 Min[j]mind[i1][k],jk 。这样方程就变成了 d[i][j]=Min[j]+a[i]j ,复杂度为 O(n2) 。第二, a[i] 的上界为 109 ,那么 d[i][j] 将会很大。这个问题可以通过将数组 a 离散化来解决。这样空间复杂度也被优化到 O(n2) 了。
这样以来整个问题就解决了。但是原问题似乎还没得到解决。事实上,我们可以简单地令所有的 a[i]=a[i]i 。就能将问题转化成我们刚刚讨论的问题,这样原问题也顺利得解了。

代码

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

typedef long long ll;
const int maxn = 3010;
int n, m, a[maxn], b[maxn];
ll ans, Min[maxn], d[maxn][maxn];

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        a[i] = a[i] - i;
        b[i] = a[i];
    }
    // 离散化中的排序
    sort(b + 1, b + n + 1);
    // 离散化中的去重
    m = unique(b + 1, b + n + 1) - b - 1;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= m; j++) {
            d[i][j] = Min[j] + abs(b[j] - a[i]);
        }
        Min[1] = d[i][1];
        // 处理前缀最小值数组
        for(int j = 2; j <= m; j++) {
            Min[j] = min(d[i][j], Min[j-1]);
        }
    }
    printf("%I64d\n", Min[m]);
    return 0;
}

(其它题目略)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值