CF Round 813 Div2 题解

比赛链接

A题 Wonderful Permutation(签到)

给定一个长度为 \(n\) 的排列 \(\{p_n\}\),你可以执行操作,每次将两个位置的数交换,问至少需要交换几次,才能使得前 \(k\) 个数的和最小?

\(T\leq 100,k\leq n\leq 100\)

显然,要把 \([1,k]\) 内所有元素给放进前 \(k\) 位里面。我们可以转化一下,把所有小于等于 \(k\) 的元素记为 0,反之记为 1,将原数组变成一个 01 数组,问需要多少次操作,才能使得前 \(k\) 位都变成 0?那显然就是前 \(k\) 位里面 1 的个数了。

做法:扫一遍,统计前 k 位里面小于等于 \(k\) 的值的个数即可,一次操作就能消掉一个。

#include<bits/stdc++.h>
using namespace std;
const int N = 210;
int n, k, p[N];
int solve()
{
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> p[i];
    int tot = 0;
    for (int i = 1; i <= k; ++i)
        if (p[i] > k) ++tot;
    return tot;
}
int main()
{
    int T;
    cin >> T;
    while (T--)
        cout << solve() << endl;
    return 0;
}

B题 Woeful Permutation(数学)

给定 \(n\),要求构造一个排列 \(\{p_n\}\),使得 \(\sum\limits_{i=1}^n\operatorname{lcm}(i, p[i])\) 最大。

\(T\leq 10^3,n,\sum n\leq 10^5\)

如果去掉 lcm,纯粹让乘积和最大,那么根据排序不等式,构造 \(\{p_n\}=\{1,2,\cdots,n\}\) 即可。不过问题在于,lcm 意味着要除以一个 gcd,那么我们假设最优情况下,都有 \(\gcd(i,p_i)=1\),那么我们想到了临项交换法,即 \(\gcd(x,x-1)=1\) 的性质。为了最小程度降低交换项所带来的值减小,我们选择按照 \((n,n-1),(n-2,n-3),\cdots,(2,1)\) 这样子来交换(例如 \(n=5\) 时构造 \((1,3,2,5,4)\))。假定 \(n=2k\),那么这种构造式的值仅比上限值小了 \(k\),加上 lcm 的限制,有理由相信这就是最优方案(也确实如此)。

官方题解的证明还挺长的,但是根据考试时候飞速上涨的过题速度,看样子大家都很快想到了这个。

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

C题 Sort Zero(思维,枚举,桶)

给定一个长度为 \(n\) 的数列 \(\{a_n\}\),现在,我们可以进行若干次操作,操作方式和效果如下:

  1. 选定一个数 \(x\)
  2. 将数列内所有值为 \(x\) 的项变为 0

问需要至少多少次操作,可以使得数列变得单调不降?

\(T\leq 10^4,n,\sum n\leq 10^5,1\leq a_i\leq n\)

经过好一会的观察,突然发现了操作的性质:他只能将值给变小,而无法变大。

假定现在存在着 \(a_i>a_j(i<j)\),我们不可能把 \(a_j\) 改的大于等于 \(a_i\),唯一的方式就是把 \(a_i\) 改成 0,而 \(a_i\) 一旦改成 0,意味着前面所有数都比它大,统统都得改成 0。

那么,我们从尾向头,一旦出现某个位置不符合单调不降数列的性质,就将其以及前面的数都变成 0?我们考虑这样一种情况:\([5,1,3,2,5]\),在 \(i=3\) 处发现了异常,意味着必须 \([1,3]\) 全部清零,但是对位置 1 的数进行操作时,我们又把位置 5 的数给搞没了,所以实际上删除的区间是 \([1,5]\)

所以,我们还需要维护一个 \(\operatorname{farPos}[x]\) 的数组,表示 \(x\) 最靠后的位置是哪里,一旦对 \(x\) 执行了删除操作,那么就要执行 \(pos=\max(pos,\operatorname{farPos[x]})\) 操作。

#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n, a[N], farPos[N];
int solve()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
        cin >> a[i], farPos[a[i]] = i;
    int pos = 0;
    for (int i = n - 1; i >= 1; i--)
        if (a[i] > a[i + 1]) {
            pos = i; break;
        }
    for (int i = 1; i <= pos; ++i)
        pos = max(pos, farPos[a[i]]);
    //[1, pos]
    set<int> s;
    for (int i = 1; i <= pos; ++i) s.insert(a[i]);
    return s.size();
}
int main() {
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

D题 Empty Graph(思维)

给定一个长度为 \(n\) 的序列 \(\{a_n\}\),现在我们可以进行至多 \(k\) 次操作, 每次操作可以将某个数改为 \([1,10^9]\) 间的任意值。

这张图是有其现实意义的:我们构造一张 \(n\) 点的无向完全图,\((x,y)\) 间有一条边权为 \(\min\limits_{x\leq i\leq y}a_i\) 的边(要求 \(x<y\)),记 \(d(u,v)\)\((u,v)\) 之间的最短路,那么尝试使得 \(\max\limits_{1\leq x<y\leq n} d(u,v)\) 最大。

\(2\leq n\leq 10^5,1\leq k\leq n,1\leq a_i\leq 10^9\)

对着题面看了四十分钟后,我意识到 \((u,v)\) 之间的最短路无非这几种:

  1. 直接从 u 到 v
  2. 从 u 到 1,然后从 1 到 v
  3. 从 v 到 n,再从 n 到 u
  4. 从 u 到 1,从 1 到 n,再从 n 到 v

再归纳一下,我们得到了一条相对通用的结论:若存在某个点的权值为 \(x\),那么图上的最短路的最大值小于等于 \(2x\):因为以此为中转点,那么最短路肯定是小于等于 \(2x\) 的。那么,我们直接将最小的 \(k\) 个点给改成 \(10^9\),然后看看剩下来最小的是啥,输出它的两倍就行了 ?

我们通过上面的推导,得到了如下结论:两点之间的最短路,要么是直接走过去,要么是找权值最小的点中转一下。如果所有最短路都是通过直接走的方式得到,那么有可能完全不需要通过中转点来实现。

因此,我们有:

\[ans=\min(\max\limits_{1\leq i<n}\min(a_i,a_{i+1}),2\min\limits_{1\leq i \leq n}a_i) \]

已知我们不能无脑把前 k 个元素给改成 \(10^9\),但是我们可以选择把前 k-1 个元素改成 \(10^9\),随后看一下第 \(k\) 个改成啥。

  1. 找一个 \(10^9\) 贴着,然后答案必然是两倍最小值
  2. 填在第 \(k\) 位置,然后扫一遍可能答案
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5 + 10, INF = 1e9;
int n, m, a[N], b[N], k;
struct Node {
    int id, w;
    bool operator < (const Node &rhs) const {
        return w < rhs.w;
    }
} p[N];

int solve()
{
    //read
    cin >> n >> k;
    for (int i = 1; i <= n; ++i)
        cin >> a[i], p[i] = (Node){i, a[i]};
    //solve
    int res1 = INF, res2 = INF;
    sort(p + 1, p + n + 1);
    for (int i = 1; i < k; ++i)
        p[i].w = a[p[i].id] = INF;
    sort(p + 1, p + n + 1);
    // 加 k-1 次,最大值在最小值*2和最大值的最小值之间产生
    res1 = min(res1, p[1].w * 2);
    res1 = min(res1, p[n].w);
    // 加 k 次,最大值在min(a[i], a[i + 1])的最大值和最小值*2之间产生
    p[1].w = a[p[1].id] = INF;
    sort(p + 1, p + n + 1);
    res2 = min(res2, p[1].w * 2);
    int temp = -INF;
    for (int i = 1; i <= n; i++)
        a[p[i].id] = p[i].w;
    for (int i = 1; i < n; i++)
        temp = max(temp, min(a[i], a[i + 1]));
    res2 = min(temp, res2);
    //
    return max(res1, res2);
}

int main() {
    int T;
    cin >> T;
    while (T--) cout << solve() << endl;
    return 0;
}

话说官方似乎还提供了一个二分的做法,就是二分这个最短路长度,那么看看至少需要多少次操作。

E1题 LCM Sum (easy version)(枚举,数学)

给定一段区间 \([l,r]\),问有多少符合要求的三元组 \((i,j,k)\),满足 \(l\leq i<j<k\leq r\),且 \(\operatorname{lcm}(i,j,k)\geq i+j+k\)

\(1\leq T\leq 5,1\leq l,r\leq 2*10^5,l+2\leq r\)

考虑到符合要求的组数会很多,所以我们考虑统计小于的三元组数目。

因为 \(l\leq i<j<k\leq r\),所以 \(\operatorname{lcm}(i,j,k)<i+j+k<3k\),所以 \(\operatorname{lcm}(i,j,k)\) 的取值只能是 \(k\) 或者 \(2k\)

如果是 \(k\),那就说明 \(i,j\) 都是 \(k\) 的因数,慢慢枚举就好;如果是 \(2k\),再暴力一遍的复杂度有点大(虽然也只是常数上面的问题),官方似乎直接暴力怼的,我在知乎上面关注的两个博主都是打表找规律(两种情况:\((3,4,6),(6,10,15)\),但是 \((1,2,3)\) 不行,因为 \(\operatorname{lcm}(1,2,3)=6=1+2+3\))。

我们直接预处理出 \(4*10^5\) 中每个数的因数有哪些,总计 \(\sum\limits_{i=1}^n\frac{2n}{i}=O(n\log n)\) 级别,随后暴力枚举即可。但是枚举因数并不是 \(O(\log^2 n)\) 的,我按照最多因数跑了一下,可能有 \(O(\sqrt{n})\) 级别,所以总复杂度是一个跑不满的 \(O(n\sqrt{n})\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 400010;
vector<int> fac[N];
int main()
{
    for (int i = 1; i <= 2e5; i++)
        for (int j = i; j <= 4e5; j += i)
            fac[j].push_back(i);
    int T;
    cin >> T;
    while (T--) {
        int L, R;
        cin >> L >> R;
        LL len = R - L + 1;
        LL ans = len * (len - 1) * (len - 2) / 6;
        for (int k = L; k <= R; k++) {
            //k
            LL cnt = 0;
            for (int x : fac[k])
                if (L <= x && x < k) cnt++;
            ans -= cnt * (cnt - 1) / 2;
            // 2k: (3, 4, 6), (6, 10, 15)
            if (k % 6 == 0 && k / 2 >= L) ans--;
            if (k % 15 == 0 && k * 2 / 5 >= L) ans--;
        }
        cout << ans << endl;
    }
    return 0;
}
posted @ 2022-08-14 22:50  cyhforlight  阅读(24)  评论(0编辑  收藏  举报