Codeforces Round #842 (Div. 2) A-E

比赛链接

A

题意

给一个数 k 找到最大的 x ,满足 1x<kx!+(x1)!k 的倍数。

题解

知识点:数学。

猜测 x=k1 ,证明 (k1)!+(k2)!=(k1+1)(k2)!=k(k2)!,k2

因此 x=k1

时间复杂度 O(1)

空间复杂度 O(1)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
bool solve() {
int k;
cin >> k;
cout << k - 1 << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

B

题意

给一个长为 n 的排列,每次操作从排列中取出 k 个数,从小到大排序好放回排列尾部。问最少操作多少次,才能将原排列变成从小到大排序好的排列。

题解

知识点:贪心。

注意到每次操作都会把数字放到尾部,不会影响之前数字的相对位置。因此为了使得操作最小化,我们先找到不用选的数字有多少,显然我们需要从 1 开始递增往后找。设 pos 是第一个要选的数字,那么答案便是 npos+1k

时间复杂度 O(n)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[100007];
bool solve() {
int n, k;
cin >> n >> k;
for (int i = 1;i <= n;i++) cin >> a[i];
int pos = 1;
for (int i = 1;i <= n;i++) {
if (pos == a[i]) pos++;
}
cout << (n - pos + 1 + k - 1) / k << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

C

题意

给出 n 个数 ai ,要求两个长为 n 的排列 p,q 使得 ai=max(pi,qi)

题解

知识点:构造。

先记录每个数字出现的位置 pos[a[i]] ,随后从小到大构造:

  1. 数字没出现过,那么可以放入队列 qu ,用于补齐出现两次的数字的空位。
  2. 数字只出现了一次,假设出现在 ai ,那么令 pi=qi=ai 是最优的。因为 pi,qi 其中一个可以更小,但小的数字可能要用于填充别的地方,所以最优解是填两个相等的。
  3. 数字出现了两次,假设出现在 ai=aj=a ,那么令 pi=qj=a ,设 qu 里队首元素为 x ,令 qi=pj=x 。因为是从小到大构造,所以 qu 里的元素一定是比 a 小的,所以可以用来填充空位;如果队空,则无解。
  4. 数字出现三次及以上,无解。

时间复杂度 O(n)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[200007];
int p[200007], q[200007];
vector<int> pos[200007];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i], pos[i].clear();
for (int i = 1;i <= n;i++) pos[a[i]].push_back(i);
queue<int> qu;
for (int i = 1;i <= n;i++) {
if (pos[i].size() > 2) return false;
if (pos[i].size() == 0) qu.push(i);//空闲数字放入队列
else if (pos[i].size() == 1) {
p[pos[i][0]] = i;
q[pos[i][0]] = i;
}//可以用小的但不是最优的
else {
if (qu.empty()) return false;//没有空闲的小的数字,无解
p[pos[i][0]] = i;
p[pos[i][1]] = qu.front();
q[pos[i][0]] = qu.front();
q[pos[i][1]] = i;
qu.pop();
}
}
cout << "YES" << '\n';
for (int i = 1;i <= n;i++) cout << p[i] << " \n"[i == n];
for (int i = 1;i <= n;i++) cout << q[i] << " \n"[i == n];
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << "NO" << '\n';
}
return 0;
}

D

题意

给一个长为 n 的排列,每次可以选择两个数交换,问最少交换几次可以使得排列逆序数为 1

题解

知识点:枚举,数学。

关于这类排列的题,都可以先进行一个构造,连接所有 ia[i] ,图中会形成若干个环,称为置换环。例如 2,3,4,1,5 ,可以得到 1,2,3,4 构成的环和 5 构成的环( 5 是自环)。

我们进行一次交换操作 (i,j),将使得 iai,jaj 两条边变成 iaj,jai 。这个操作在图中可以做到以下两个结果之一:

  1. 一个环被裂解成两个环
  2. 两个环被合并成一个环

前提是不破坏相对元素的位置,例如 1,3,2,4 环不可能分解成 1,23,4 环;1,23,4 环也不可能合并成 1,3,2,4 环。

举个例子,我们对 2,3,4,1,5 交换 (2,4) ,则排列变成 2,1,4,3,5 ,图中边 23,41 变成 21,43 ,即 1,2,3,4 环被拆成 1,23,4 两个环;或者交换 (4,5) ,则排列变成 2,3,4,5,1 ,图中边 41,55 变成 45,51 ,即 1,2,3,45 环被合成为 1,2,3,4,5 环。

回到题目。题目要求的最终状态化成图后,实际上就是一组相邻元素成环,剩下的元素自环。

我们对原排列化为置换环图,假设这些环中已经有至少一组相邻元素(环中位置不一定需要相邻,因为可以通过操作使其相邻),如 1,4,2 环就有 1,2 两个相邻元素,我们可以在之后的操作中保留这组元素,把其他元素全都操作成自环即可;如果没有,那么先将元素都操作成自环,再多一次操作把一组相邻元素合并成环,如排列 3,4,5,2,11,3,52,4 环,一个相邻元素都没有。

假设 n 个元素的图中有 cnt 个环,那么如果我们需要把环中元素都操作成自环,实际上需要操作 ncnt 次,因为每个环保留一个元素,剩下的元素都需要通过操作挪出来。再考虑相邻元素的结论,如果有相邻元素那么可以少操作一次 ncnt1 ;否则需要多操作一次 ncnt+1

环的实现可以看代码,和并查集类似但简单许多。

时间复杂度 O(n)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int a[200007];
int fa[200007];
bool solve() {
int n;
cin >> n;
for (int i = 1;i <= n;i++) cin >> a[i], fa[i] = -1;
int ans = n;
for (int i = 1;i <= n;i++) {
if (fa[i] != -1) continue;
int j = i;
ans--;//一个环减一次,
while (fa[j] == -1) {
fa[j] = i;//环内元素的根设为i
j = a[j];
}
}
bool ok = 0;
for (int i = 1;i <= n - 1;i++) ok |= fa[i] == fa[i + 1];//环内有一队相邻元素,可以少操作一次
cout << ans + (ok ? -1 : 1) << '\n';
return true;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t = 1;
cin >> t;
while (t--) {
if (!solve()) cout << -1 << '\n';
}
return 0;
}

E

题意

对一个长度为 3n 的排列 p,有两种操作:

  1. 对前 2n 个元素从小到大排序
  2. 对后 2n 个元素从小到大排序

定义 f(p) 为将排列 p 通过操作从小到大排序好的最少次数。

现在给出一个 n ,求长度为 3n(3n)! 个排列的 f(p) 之和。

题解

知识点:容斥原理,排列组合。

显然,对于任意排列 pf(p)3 ,只要使用操作 1,2,1 就能排序好。

现在分类讨论 f(p) ,求对应的种类数。注意到,求精确 f(p)=1,2,3 的种类数比较困难,因此考虑求 f(p)1,f(p)2,f(p)3 的情况,最后作差即可。

  1. f(p)=0

显然只有一种 1,,3n

  1. f(p)1

至多一次操作就可以排序好,那么一定是前 n 个或者后 n 个元素已经排好了,其他元素自由排列,对剩下部分操作一次即可。

n 个元素排好了,后 2n 个自由排列,一共 (2n)! 种。同理,后 n 个元素排好了也有 (2n)! 种。

当然,最后需要减去交集,即前 n 个元素和后 n 个元素同时排好了,则中间 n 个元素自由排列,有 n! 种。

因此,最终有 2(2n)!n! 种。

  1. f(p)2

至多两次操作就可以排序好,那么元素 [1,n] 需要出现在 [1,2n] 的区间里,其他元素自由排列,这样对 [1,2n] 操作一次前 n 个就排好了,再对 [n+1,3n] 操作一次即可,同理元素 [2n+1,3n] 出现在 [n+1,3n] 其他元素自由排列也可以,只需要最多操作两次。

元素 [1,n] 出现在 [1,2n] ,那么在 2n 个位置里选 n 个位置放这 n 个元素,并且这 n 个元素可以自由排列,剩下 2n 个元素也可以自由排列,因此有 C2nnn!(2n)! 种。同理,元素 [2n+1,3n] 出现在 [n+1,3n] 也有 C2nnn!(2n)! 种。

最后减去交集部分,即元素 [1,n] 出现在 [1,2n] 同时元素 [2n+1,3n] 出现在 [n+1,3n] 。直接算比较困难,考虑设元素 [1,n] 种有 i 个元素出现在 [n+1,2n] 中,则元素 [1,n]ni 个元素出现在 [1,n] 中。于是,对于元素 [1,n] ,在 [1,n]ni 个位置,在 [n+1,2n]i 个位置,并任意排列,有 CnniCnin! 种;对于元素 [2n+1,3n] ,因为 [n+1,2n]i[1,n] 的元素,所以在 [n+1,3n]2ni 个位置可以选,其中中选 n 个,并任意排列,有 C2ninn! 种;剩下 n 个元素任意排列有 n! 种,共有 CnniCnin!C2ninn!n! 种。 最终把 i[0,n] 累加一遍就是交集部分。

因此,最终有 2C2nnn!(2n)!i=0n(CnniCnin!C2ninn!n!) 种。

  1. f(p)3

全排列即可,共有 (3n)! 种。

最后,做差就可以得到 f(p)=1,2,3 的种类数,分别加权和即可。

时间复杂度 O(n)

空间复杂度 O(n)

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int M;
int qpow(int a, int k) {
int ans = 1;
while (k) {
if (k & 1) ans = 1LL * ans * a % M;
k >>= 1;
a = 1LL * a * a % M;
}
return ans;
}
int fac[3000007], invfac[3000007];
void init(int n) {
fac[0] = 1;
for (int i = 1;i <= n;i++) fac[i] = 1LL * fac[i - 1] * i % M;
invfac[n] = qpow(fac[n], M - 2);
for (int i = n;i >= 1;i--) invfac[i - 1] = 1LL * invfac[i] * i % M;
}
int C(int n, int m) {
if (n < m || m < 0) return 0;
return 1LL * fac[n] * invfac[m] % M * invfac[n - m] % M;
}
int main() {
std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n;
cin >> n >> M;
init(3 * n);
int ans[4];
ans[0] = 1;
ans[1] = (1LL * 2 * fac[2 * n] % M - fac[n] + M) % M;
ans[2] = 1LL * 2 * C(2 * n, n) % M * fac[n] % M * fac[2 * n] % M;
for (int i = 0;i <= n;i++) {
ans[2] = (ans[2] - 1LL * C(n, i) * C(n, n - i) % M * fac[n] % M * C(2 * n - i, n) % M * fac[n] % M * fac[n] % M + M) % M;
}
ans[3] = fac[3 * n];
ans[1] = (ans[1] - ans[0] + M) % M;
ans[2] = ((ans[2] - ans[1] + M) % M - ans[0] + M) % M;
ans[3] = (((ans[3] - ans[2] + M) % M - ans[1] + M) % M - ans[0] + M) % M;
int res = ((ans[1] + 1LL * 2 * ans[2] % M) % M + 1LL * 3 * ans[3] % M) % M;
cout << res << '\n';
return 0;
}
posted @   空白菌  阅读(241)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
点击右上角即可分享
微信分享提示