CF教育场131 题解

官方题解

A题 Grass Field(签到)

给定一个两行两列的方格,每个格子里面会有草(或者没东西,就是空地)。每次操作,我们可以选定一行和一列,随后将这行和这列上的草全部清空。

给定一种情况,问至少需要多少次操作才能完全清空草。

四个格子都没草的话就是 0 次,四个格子都有的话就 2 次。

其他任何情况,都仅仅需要一次操作即可(1个草和3个草显然,2个草可以视为3个草的弱化版)。

#include<bits/stdc++.h>
using namespace std;
int solve() {
    int tot = 0, x;
    for (int i = 0; i < 4; ++i)
        cin >> x, tot += (x == 1);
    if (tot == 0) return 0;
    if (tot == 4) return 2;
    return 1;
}
int main()
{
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

B题 Permutation(思维)

给定一个排列 \({b_n}\) 和常数 \(d\),倘若有 \(x\)\(i\) 满足 \(d*b_i=b_{i+1}(1\leq i<n)\),那么这个排列的满意度就是 \(x\)

给定 \(n\),要求求出满意度最高的排列(\(d\) 可以自己选),多解的情况下输出任意一种即可。

\(2\leq n\leq 2*10^5\)

严格证明不太会,但是从感性角度,显然应该取 \(d=2\),然后尽可能的造等比数列,就像下面的代码一样(有点 \(O(n\log n)\) 那种素数筛的感觉了)。

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, vis[N], a[N];
void solve() {
    cin >> n;
    memset(vis, 0, sizeof(int) * (n + 1));
    int tot = 0;
    for (int i = 1; i <= n; ++i)
        if (!vis[i]) {
            for (int j = i; j <= n; j *= 2)
                a[++tot] = j, vis[j] = 1;
        }
    printf("2\n");
    for (int i = 1; i <= n; ++i)
        printf("%d ", a[i]);
    puts("");
}
int main()
{
    int T;
    cin >> T;
    while (T--) solve();
    return 0;
}

C题 Schedule Management(贪心,二分)

给定 \(n\) 个工人和 \(m\) 个任务,现在要把任务分配给工人们去做,每个工人独立的同时开始工作,最后的总花费时间为所有工人所花时间的最大值(取 max)。不过,现在存在着任务熟练度的情况,即如果第 \(a_i\) 个工人去做第 \(i\) 个任务,那么只需要一个时间单位,否则则需要两个时间单位。

给定 \(n,m,\{a_n\}\),试求出所花时间的最小值。

\(1\leq n,m\leq 2*10^5\)

显然,时间可以二分,那么我们直接看咋判断给定时间能不能完成就行了。

根据贪心原则,每个任务都应该优先分给熟练这个任务的人(除非他确实负载满了),随后我们记一下剩下来没被分配的任务数量为 \(tot\),接着扫一遍所有工人,有剩的时间就塞点任务,最后看看有没有剩下来的任务就行。

二分范围:至少需要时间 \(1\),至多需要时间 \(2m\)

综上,时间复杂度为 \(O((n+m)\log m)\)

#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m, a[N], h[N];
bool check(int x) {
    memset(h, 0, sizeof(int) * (n + 1));
    int tot = 0;
    for (int i = 1; i <= m; ++i)
        if (h[a[i]] == x) ++tot;
        else h[a[i]]++;
    //这里有个小坑
    //如果你只管一直减,最后才判断tot是不是小于等于0
    //极限数据的情况下会直接爆int
    //所以我们采取了边减边判断的方式,这样就不会爆int了
    for (int i = 1; i <= n; ++i) {
        tot -= (x - h[i]) / 2;
        if (tot <= 0) return true;
    }
    return false;
}
int solve() {
    cin >> n >> m;
    for (int i = 1; i <= m; ++i)
        cin >> a[i];
    int l = 0, r = 2 * m + 1;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}
int main()
{
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

D题 Permutation Restoration(数论分块,贪心)

给定正整数 \(n\) 和排列 \(\{a_n\}\),我们就可以构造出新数列 \(b_i=\lfloor\frac{i}{a_i}\rfloor\)

现在给定了 \(n\)\(\{b_n\}\),要求反向构造出合法的排列 \(\{a_n\}\)

\(1\leq n\leq 5*10^5,0\leq b_i\leq 5*10^5\),保证一定有合法解

显然,对于每个位置,我们需要知道它对应可以填哪些数,而这些数是连续的,那只要求出对应区间即可。

这是数论分块的内容(虽然我tm看了半小时 OI Wiki都没推出来公式,麻了),这里给出推导:

\[b_i=\lfloor\frac{i}{a_i}\rfloor \\ b_i\leq \frac{i}{a_i}<b_i+1 \\ \frac{i}{b_i+1}<a_i\leq \frac{i}{b_i} \]

得到了每个位置对应的区间,我们看咋进行匹配(要是能缩小数据范围到 500 就好了,直接二分图匹配啥的莽上去)。

考试时候有这么一个思路:按照左端点第一关键字,右端点第二关键字的方式来排序,随后贪心选择即可,但是显然,这有一个 bug:\([2,3],[2,4],[2,5],[3,3]\)(纯展示用,应该不会真的出现这种数据)。

考虑优化一下:按照左端点来排序,但是同时需要动态为左端点进行修改(例如上面,把 2 赋给第一个区间后,后面所有区间的左端点全部改成 3 或者更大的值)。

注意到,只有当选择的数达到 \(x\) 时,才会把左端点为 \(x\) 的数放进备选空间,而一旦放进去后,则只和右端点有关,那么我们就按照这个规律,创建一个新集合 \(S\),每当枚举到一个数 \(x\) 时,把所有新的符合要求的点放进去,然后从中选取右端点最小的即可。(因为保证有解,所以实际上还可以省略很多花里胡哨操作)

总时间复杂度:\(O(n\log n)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
int n, a[N], b[N];
struct Node { int l, r, id; } nd[N];
set<pair<int, int> > s;
void solve() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        cin >> b[i];
    for (int i = 1; i <= n; ++i) {
        nd[i].r = b[i] > 0 ? i / b[i] : n;
        nd[i].l = i / (b[i] + 1) + 1;
        nd[i].id = i;
    }
    sort(nd + 1, nd + n + 1, [](const Node &a, const Node &b) {
        return a.l < b.l;
    });
    for (int i = 1, j = 1; i <= n; ++i) {
        for (; nd[j].l == i && j <= n; ++j) {
            int id = nd[j].id;
            s.insert({nd[j].r, nd[j].id});
        }
        a[s.begin()->second] = i;
        s.erase(s.begin());
    }
    for (int i = 1; i <= n; ++i)
        printf("%d ", a[i]);
    puts("");
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) solve();
    return 0;
}
posted @ 2022-07-10 18:57  cyhforlight  阅读(15)  评论(0编辑  收藏  举报