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\}\),现在,我们可以进行若干次操作,操作方式和效果如下:
- 选定一个数 \(x\)
- 将数列内所有值为 \(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)\) 之间的最短路无非这几种:
- 直接从 u 到 v
- 从 u 到 1,然后从 1 到 v
- 从 v 到 n,再从 n 到 u
- 从 u 到 1,从 1 到 n,再从 n 到 v
再归纳一下,我们得到了一条相对通用的结论:若存在某个点的权值为 \(x\),那么图上的最短路的最大值小于等于 \(2x\):因为以此为中转点,那么最短路肯定是小于等于 \(2x\) 的。那么,我们直接将最小的 \(k\) 个点给改成 \(10^9\),然后看看剩下来最小的是啥,输出它的两倍就行了 ?
我们通过上面的推导,得到了如下结论:两点之间的最短路,要么是直接走过去,要么是找权值最小的点中转一下。如果所有最短路都是通过直接走的方式得到,那么有可能完全不需要通过中转点来实现。
因此,我们有:
已知我们不能无脑把前 k 个元素给改成 \(10^9\),但是我们可以选择把前 k-1 个元素改成 \(10^9\),随后看一下第 \(k\) 个改成啥。
- 找一个 \(10^9\) 贴着,然后答案必然是两倍最小值
- 填在第 \(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;
}