CodeForces Round 959 (PARTIAL) Solution
0. 闲话
欢迎来到有史以来最简单的一场 Div. 1 + Div. 2.
题有 \(8\) 道,AC \(5\) 道。
然而因为手速不够快惜败于 dzj
大佬。
这件事情告诉我们,不要在打 CF 的时候做其他事情。
详情见下。
1. Diverse Game / 红
给一个大小为 \(nm\) 的排列,让我构造一个大小为 \(nm\) 且每个元素都与原排列不同的排列。
好啊你 CF,你终于给了一次真正的签到题,之前你给的签到题思维难度起码到黄。
直接让每个元素模 \(nm\) 再加 \(1\) 即可。正确性显然。时间复杂度 \(O(nm)\)。
#include <iostream>
using namespace std;
const int N = 5e4 + 10;
int n, m;
void run()
{
scanf("%d%d", &n, &m);
if (n == 1 and m == 1)
{
scanf("%*d");
puts("-1");
return;
}
for (int i = 1, x; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
scanf("%d", &x);
printf("%d%c", x % (n * m) + 1, " \n"[j == m]);
}
}
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}
2. Fun Game / 橙
给两个 \(01\) 序列 \(a\) 和 \(b\),可以进行无限次操作使 \(a_l\) 到 \(a_r\) 同时异或 \(a_1\) 到 \(a_{r-l+1}\)。问将 \(a\) 通过操作转换为 \(b\) 的可行性。
我看到题目后想,这啥?第二题就上强度啊?
然后我想起了赛前刷的绝区零视频。我突然意识到,可以将 \(a\) 和 \(b\) 内的前缀零看成“空洞”,因为根据题目的要求,前缀零无论进行多少次操作,长度都不会减小。
而在初始空洞后的所有元素,可以依靠空洞后的第一个 \(1\) 修改成任意的样子。
于是问题解决。核心代码一行。时间复杂度 \(O(n)\)。
#include <iostream>
#include <string>
using namespace std;
const int N = 1e5 + 10;
int n;
string s, t;
void run()
{
cin >> n >> s >> t;
puts(s.find('1') <= t.find('1') ? "YES" : "NO");
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}
3. Hungry Games / 黄
有一排蘑菇。
每次可以选择一个区间依次吃蘑菇,初始受毒性为 \(0\),吃掉第 \(i\) 个蘑菇会使得受毒性增加 \(a_i\)。每次吃掉一个蘑菇后,若受毒性超过一个常数 \(x\),则将受毒性归零。问有多少个区间使得依次吃完蘑菇后的受毒性不为 \(0\)。
正难则反。虽然正也不难,但是反更好想。
结果不为 \(0\)=总区间数-结果为 \(0\)。
计算方式类似拓扑排序。
记 \(d_i\) 表示以 \(i\) 为区间右端点时有多少个左端点 \(j\) 满足从 \(j\) 吃到 \(i\) 后受毒性为 \(0\)。
遍历到一个位置 \(i\) 时,找到第一个满足 \(\sum_{j=i}^t a_j>x\) 的端点 \(t\),将 \(d_t\) 增加 \(d_i+1\)。因为,\(d_i\) 所记录的方案满足最终受毒性为 \(0\),而从 \(i\) 到 \(t\) 的最终受毒性也是 \(0\),所以可以看作将两个区间拼接起来。最终受毒性仍然为 \(0\)。
时间复杂度 \(O(n)\)。
#include <iostream>
#include <cstring>
using namespace std;
const int N = 2e5 + 10;
using ll = long long;
int n, k, r;
ll a[N], dp[N], cur, res;
void run()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++)
{
scanf("%lld", a + i);
}
memset(dp + 2, 0, n << 3);
cur = 0;
r = 1;
for (int i = 1; i <= n; i++)
{
cur -= a[i - 1];
while (r <= n and cur <= k)
cur += a[r], r++;
if (cur <= k)
continue;
dp[r] += dp[i] + 1;
}
res = n;
res *= n + 1;
res /= 2;
for (int i = 2; i <= n + 1; i++)
{
res -= dp[i];
}
printf("%lld\n", res);
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}
4. Funny Game / 绿
有一个拥有 \(n\) 个节点的完全图。每个点有一个点权 \(a_i\)。点 \(i\) 和点 \(j\) 之间的边的边权为 \(|a_i-a_j|\)。要求找出图中的一个生成树,使得这个生成树中的第 \(i\) 条边的边权整除 \(i\)。\(i\) 从 \(1\) 开始计数。
当时我先于两位大佬 A 掉了第三题,然后觉得,对于一场 Div. 2 来说,这已经是我的极限了,于是去刷绝区零的视频。正当我欣赏格莉丝代理人秘闻的时候,我惊恐地发现那两位大佬已经 A 掉了这一题。然后我的大脑开始飞速运转。
于是突然想到 \(i\) 越大期望被整除的边就越少,考虑贪心+并查集。具体来说从大到小枚举每个 \(i\),计算每个 \(a_j\bmod i\) 的结果,如果发现有两个 \(a_j\) 余数相同但不在同一连通块内,就将这两个连通块合并。
当我发现它 A 了的时候,我的大脑炸了。
时间复杂度 \(O(n^2)\)。
#include <iostream>
#include <list>
#include <cstring>
using namespace std;
const int N = 2e3 + 10;
int n, a[N], f[N], v[N], p;
bool fl;
using pii = pair<int, int>;
list<pii> ret;
inline int find(int x)
{
return x == f[x] ? x : f[x] = find(f[x]);
}
inline void merge(int x, int y)
{
f[find(y)] = find(x);
}
void run()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
f[i] = i;
scanf("%d", a + i);
}
ret.clear();
for (int i = n - 1; i; i--)
{
memset(v, 0, i << 2);
fl = false;
for (int j = 1; j <= n; j++)
{
p = a[j] % i;
if (!v[p])
{
v[p] = j;
continue;
}
if (find(j) == find(v[p]))
continue;
merge(j, v[p]);
ret.push_front({j, v[p]});
fl = true;
break;
}
if (!fl)
return puts("NO"), void();
}
puts("YES");
for (auto &[tx, ty] : ret)
printf("%d %d\n", tx, ty);
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}
5. Wooden Game / 绿
有个由 \(k\) 棵树组成的森林,每次可以移除任意一个节点为根的子树,求所有移除的子树大小的最大或值。
打完上一题,我夹在两个大佬中间。我想,应该不会再有 A \(5\) 题那么离谱的 Div. 1 + Div.2 了罢。然后去看莱卡恩的代理人秘闻。
万万没想到,还没看一会,那两位大佬就又把这题 A 掉了。
吓得我赶紧回来使大脑飞速运转。
我本来想着各种复杂的 DP,然后就突然想到可以移除叶子。也就是说,一个大小为 \(x\) 的子树对答案的贡献可以是 \([1,x]\) 中的任意一个整数。
当我想到这时,我的脑子再一次被炸掉了。它给的树结构压根就没有用。
时间复杂度 \(O(n)\)。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 1e6 + 10;
int n, a[N], t, res;
void run()
{
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", a + i);
for (int j = 1; j < a[i]; j++)
scanf("%*d");
}
sort(a + 1, a + n + 1);
t = 1;
for (; t < a[n]; t = t * 2 + 1)
;
res = 0;
for (int i = n; i; i--)
{
while ((t > a[i]) or (t & res))
t--;
res |= t;
}
printf("%d\n", res);
}
int main()
{
int T = 1;
scanf("%d", &T);
while (T--)
run();
}
6. 后记
然后两位大佬的其中一位把第 \(6\) 题过了。
比赛完后发现,出题人竟然在第 \(5\) 题的题解中定义一个 trash
变量然后输入 \(n-1\) 次……真够幽默的。
然后飞升了。加了 \(117\)。
我只能说:
74TrAkToR 你干得好啊!