Codeforces Round 894 (Div. 3) 【题解A-G】

文章目录

  • Codeforces Round 894 (Div. 3)
    • A. Gift Carpet(模拟)
    • B. Sequence Game(构造)
    • C. Flower City Fence(差分、双指针)
    • D. Ice Cream Balls(二分)
    • E. Kolya and Movie Theatre(优先队列+贪心)
    • F. Magic Will Save the World(背包dp)
    • G. The Great Equalizer(set)

Codeforces Round 894 (Div. 3)

A. Gift Carpet(模拟)

题意:判断从左往右每列取最多一个字母,是否能够取出vika。

思路:贪心思想,如果一个字母能在前面取到,也可以在后面取到,我们应该选择在前面取到,这样给剩下的字母选择的空间更大,更能够取到剩下的字母,更能够取出这个子序列。注意,后面的字母必须在前面的字母取了才能取,这样依次贪心最早出现的位置,可以保证每个字母都是最优的选择,从而保证整体最优。

实现:

string s = "vika";
char mp[25][25];

void solve() {
    int n, m;
    cin >> n >> m;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> mp[i][j];
        }
    }
    
    int p = 0;
    for (int j = 0; j < m; j++) {
        for (int i = 0; i < n; i++) {
            if (mp[i][j] == s[p] && p < 4) {
                p++;
                break;
            }
        }
    }
    cout << (p == 4 ? "YES\n" : "NO\n");
}

补充说明:关于s[s.size()]的问题,stkovflow上面有解答。传送门,s[s.size()]必定是’\0’

B. Sequence Game(构造)

题意:让你构造一个原数组,经过如下操作可以得到已知数组,首先写下第一个数,接下来,每次写下,大于等于前面的数的数。

思路:主要判断一下上一个写的数是不是1,如果不是1,我们添个1,这个1小于前面的数,不会被选中,再再这个1后面添加当前的数,当前的数必定会被选中;如果是1,不添1,直接填当前数即可,当前的数必定被选中。

实现:

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    vector<int> ans;
    for (int i = 0; i < n; i++) {
        if (i == 0) {
            ans.push_back(a[i]);
        } else {
            if (a[i - 1] != 1) {
                ans.push_back(1);
            }
            ans.push_back(a[i]);
        }
    }
    int sz = ans.size();
    cout << sz << '\n';
    for (int i = 0; i < sz; i++) {
        cout << ans[i] << " \n"[i == sz - 1];
    }
}

C. Flower City Fence(差分、双指针)

题意:给出一个单调不升的数组,画成统计图,证明这个图是对称的

思路一:差分,前缀和,首先任何一个数不可能大于n,考虑每段对这些位置的高度的贡献。

实现一:

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    vector<int> sum(n + 1);
    for (int i = 0; i < n; i++) {
        if (a[i] > n) {
            cout << "NO\n";
            return;
        }
        sum[0]++;
        sum[a[i]]--;
    }
    vector<int> b(n);
    b[0] = sum[0];
    int tmp = b[0];
    for (int i = 1; i < n; i++) {
        tmp += sum[i];
        b[i] = tmp;
    }
    cout << (a == b ? "YES\n" : "NO\n");
}

思路二:双指针,数组是有序的,我们观察上面的差分情况,按照影响范围依次减去贡献,小于当前位置的结束的都要减去。

实现二:

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    for (int i = 0, j = n; i < n; i++) {
        while (j > 0 && a[j - 1] <= i) {
            j--;
        }
        if (a[i] != j) {
            cout << "NO\n";
            return;
        }
    }
    cout << "YES\n";
}

D. Ice Cream Balls(二分)

a ∗ b ⩽ x    ⟺    a ⩽ x   /   b ( 均为正整数, / 是整数除法 ) 充分性: 如果 a ∗ b ⩽ x , 记 x = b k + r ( 其中 r < b ) , 那么 x / b = k 反证,如果 k < a ,那么 k + 1 ⩽ a 那么 a b ⩾ ( k + 1 ) b = k b + b > k b + r = x 矛盾,所以, k ⩾ a 即 , a ⩽ x   /   b 充分性成立 必要性: 若 a ⩽ x   /   b , 则有 k ⩾ a , 那么 x = b k + r ⩾ a b + r ⩾ a b 必要性成立 所以,整数的整数除法可以和浮点数除法一样移项 , 条件是等价的 \begin{align*} &a*b\leqslant x \iff a\leqslant x ~/~ b(均为正整数,/是整数除法)\\ &充分性:\\ &如果a*b\leqslant x,记x = bk+r(其中rkb+r=x\\ &矛盾,所以,k\geqslant a \\ &即,a\leqslant x ~/~ b\\ &充分性成立 \\ \\ &必要性:\\ &若a\leqslant x ~/~ b,则有k\geqslant{a},那么x=bk+r\geqslant{ab+r} \geqslant{ab}\\ &必要性成立 \\\\ &所以,整数的整数除法可以和浮点数除法一样移项,条件是等价的 \end{align*} abxax / b(均为正整数,/是整数除法)充分性:如果abx,x=bk+r(其中r<b),那么x/b=k反证,如果k<a,那么k+1a那么ab(k+1)b=kb+b>kb+r=x矛盾,所以,ka,ax / b充分性成立必要性:ax / b,则有ka,那么x=bk+rab+rab必要性成立所以,整数的整数除法可以和浮点数除法一样移项,条件是等价的

题意:如果你有若干个雪球,选两个组成一种雪糕(无序对),问组成n种雪糕最少要几种雪糕

思路:首先考虑最多几种雪球,二分答案求出来,再多一种雪球,就会超过。假定此时我们有x种雪球,那么再加一种崭新的雪球必定增加x种雪糕,就会超过。那么我们考虑部分重复,x种雪球0x-1种重复依次,就能得到x2x之间的数。

合法性:此时再少一种雪球无法到达,再少一个重复也无法到达,故是最优的

#include 
using namespace std;

void solve() {
    long long n;
    cin >> n;
    long long l = 1, r = 1e18 + 5;
    while (l < r) {
        long long mid = l + r + 1 >> 1;//查找最后一个符合条件mid * (mid - 1) / 2 <= n的值
        if (mid - 1 <= 2 * n / mid) {
            l = mid;
        } else {
            r = mid - 1;
        }
    }
    cout << r + (n - r * (r - 1) / 2) << '\n';
}

E. Kolya and Movie Theatre(优先队列+贪心)

题意:给定n, m, d和一个数组a,下标从1开始,初始位置是0,每移动一个位置,会消耗一个d的值。现在让你选择最多m个位置,使得从0出发,经过这五个位置的值的和最大。

思路:首先,我们可以观察到,无论我们选择哪几个位置,最终消耗的总量只和最后一个选择的位置有关。如果选择,1,3,5,那么我们是0->1,1->3,3->5最终消耗的总量就是 5 * d。我们考虑以每个点结束的情况,此时消耗的量是固定的,我们要求最大的值,只需要贪心选取最大的m个正值,当然,不一定有m个正值,就是当前节点的最优的情况。当我们向右移动一个单位后,我们要维护这个堆是当前前缀的最大的m个数,我们比较一下小顶堆堆顶的值是不是小于当前值,小于的话,就用这个新的值代替前面部分的最大的m个值,此时是最优的。

代码:

void solve() {
    int n, m, d;
    cin >> n >> m >> d;
    vector<int> a(n);
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    priority_queue<int, vector<int>, greater<int>> pq;
    long long ans = 0;
    long long sum = 0;
    for (int i = 0; i < n; i++) {
        if (a[i] < 0) continue;
        if (pq.size() < m) {
            pq.push(a[i]);
            sum += a[i];
        } else if (pq.size() == m) {
            if (pq.top() < a[i]) {
                sum -= pq.top();
                pq.pop();
                pq.push(a[i]);
                sum += a[i];
            }
        }
        ans = max(ans, sum - 1ll * d * (i + 1));
    }
    cout << ans << '\n';
}

F. Magic Will Save the World(背包dp)

题意:有n个怪兽,vika一秒可以生成w个水魔法,f个火魔法,初值均为0,第i个怪兽有si点力量,要想打败它需要至少si点火魔法或者水魔法。求出至少要多少分钟来打败这n个怪兽。

思路:在某一秒之后,水魔法和火魔法可以瞬间消灭所有的怪兽,其中水魔法消灭了若干只怪兽,火魔法消灭了若干怪兽,我们只要算出两种魔法分别消灭的怪兽的力量和,除以每秒产生的魔法数,分别向上取整,取大就能得到,当前怪兽分配的最小时间。我们要用填满背包求出所有可能的组合。

bitset<1000005> bt;
void solve() {
    bt.reset();
    int w, f;
    cin >> w >> f;
    int ans = 0x3f3f3f3f;
    int n;
    cin >> n;
    bt[0] = 1;
    vector<int> s(n);
    int tot = 0;
    for (int i = 0; i < n; i++) {
        cin >> s[i];
        tot += s[i];
    }
    for (int i = 0; i < n; i++) {
        bt |= bt << s[i];
    }
    for (int i = 0; i <= tot; i++) {
        if (bt[i]) {
            ans = min(ans, max((i + w - 1) / w, (tot - i + f - 1) / f));
        }
    }
    cout << ans << '\n';
}

G. The Great Equalizer(set)

题意:给你一个数组,此处有一个机器,输入一个数组,进行如下操作,排序,去重,假如有n个数,分别加上,n , n-1, … 1,再进行上述操作,直至只剩下一个数。现在有q次操作,每次把原数组的第i个数改成x,输出每次操作后的a数组放入机器得到的最后一个数。

思路:首先对于一个数组,最后的答案究竟是什么呢?我们首先排序,可以得到阶梯式上升不降的数组,我们把数划分成每个阶梯,这也就相当于去重,以阶梯为最小变化单位,从右到做分别上升1,2,3…,我们最后想要得到这些阶梯什么时候合并成一个,经过一次操作之后,相邻阶梯的差距都减小1,一些原来差距就为1的相邻阶梯此时就合并在一起了,所以最后操作的次数就是最开始阶梯的最大差值。那么答案是什么呢?我们注意到每次操作都会让最右侧阶梯抬高一格,所以最后答案就是当前原数组的最大值+当前原数组排序后的最大相邻差值。用set维护一下,修改相当于删除原来的数插入新的数,对应地,要删除,插入一些差和数。具体看代码,主要维护一个数的多重集合,一个差的多重集合。我写得应该挺清楚的。

代码:

void solve() {
    int n;
    cin >> n;
    vector<int> a(n);
    multiset<int> num;
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        a[i] = x;
        num.insert(x);
    }
    multiset<int> sub{0};//单个数的情况
    auto last = num.begin();
    for (auto it = num.begin(); it != num.end(); it++) {
        int d = *it - *last;
        if (d > 0) {
            sub.insert(d);//存入差
        }
        last = it;
    }
    int q;
    cin >> q;
    for (int u = 0; u < q; u++) {
        int i, x;
        cin >> i >> x;
        i--;
        int bef = a[i];//before
        a[i] = x;
        auto it = num.find(bef);
        //相当于lower_bound 若干相同的bef 取第一个的迭代器!

        if (next(it) == num.end()) {//如果后面没有数了,删去最后的差
            if (it != num.begin()) {//注意是否有上一个迭代器
                sub.erase(sub.find(*it - *prev(it)));
            }
        } else {//后面有数
            if (*next(it) != *it) {
                //如果相同,阶梯间差的情况没有改变,不必考虑
                //相同说明这里有多个bef,删一个不会影响
                sub.erase(sub.find(*next(it) - *it));
                //说明只有一个数,会改变
                if (it != num.begin()) {
                    // prev  it  next
                    //删去it,要删去it相邻的差,增加prev和next的差
                    sub.erase(sub.find(*it - *prev(it)));
                    sub.insert(*next(it) - *prev(it));
                }
            }
        }
        if (it != num.end())
        num.erase(it);//此时还不能插入x!
        auto up = num.upper_bound(x);//第一个大于x的位置

        if (up == num.end()) {//x最大的话
            if (num.begin() != num.end()) {
                //如果相等是就是单个数,没有差,如果prev的会越界
                sub.insert(x - *prev(num.end()));
            }
        } else if (up == num.begin()) {
            sub.insert(*num.begin() - x);
            //x最小,就直接插入一个就行    x  begin
        } else {
            if (*prev(up) != x) {//插中间
                //prev x up
                sub.erase(sub.find(*up - *prev(up)));
                sub.insert(x - *prev(up));
                sub.insert(*up - x);
            }
        }
        num.insert(x);
        cout << *sub.rbegin() + *num.rbegin() << " \n"[u == q - 1];
    }
}

你可能感兴趣的:(CF,算法,数据结构)