Codeforces Round 1042 (Div. 3) (A ~ E)
A. Lever
模拟
题意:
给定两个数组\(a\),\(b\)规定每次迭代执行以下两种操作
-
随机选择\(i\)这样的索引\(a_i\) > \(b_i\)。然后将\(a_i\)减少\(1\)。如果不存在这样的\(i\),则忽略此步骤。
-
随机选择\(i\)这样的索引\(a_i\) < \(b_i\)。然后将\(a_i\)增加\(1\)。如果不存在这样的\(i\),则忽略此步骤
每次迭代都会检查操作\(1\)是否执行如果被忽略则结束迭代,问迭代次数
思路:
迭代次数 = 计算操作\(1\)需要执行几次,所以只需对于所有满足\(a_i > b_i\)条件求两者之差再求和加\(1\)即可
代码:
void solve()
{
int n;
std::cin >> n;
std::vector<int> a(n + 1),b(n + 1);
for(int i = 1; i <= n; i ++) std::cin >> a[i];
for(int i = 1; i <= n; i ++) std::cin >> b[i];
i64 maxv = 0;
for(int i = 1; i <= n; i ++)
maxv += (a[i] > b[i] ? a[i] - b[i] : 0);
std::cout << maxv + 1 << '\n';
}
B. Alternating Series
构造
题意:
规定满足以下两个条件的数组为好数组
-
相邻元素的乘积为负
-
对于所有长度至少为\(2\)的子数组,子数组中所有元素之和为正数
规定如果一个数组的词典长度比另外一个数组小,就说前一个数组是优于后者的
输出一个长度为\(n\)的好数组,使得它优于其他所有长度为\(n\)的好数组。
思路:
首先要保证词典长度最小,那么就要尽可能的填绝对值小的数,那么问题是先填\(1\)还是填\(-1\),考虑到所有长度大于等于\(2\)的子数组和都要大于\(0\)的条件,那么在保证词典长度最小的情况下肯定负数绝对值越小越有利,这样填的正数也不会太大,这样奇数位都填\(-1\)那偶数位我们最少能填多少,不难发现如果当前不是最后一位偶数位的话,填\(3\)才能保证子数组和是正数,最后一位偶数位填\(2\)就足够了
代码:
void solve()
{
int n;
std::cin >> n;
for(int i = 1; i <= n; i ++)
{
if(i == n and i % 2 == 0) std::cout << 2 << ' ';
else if(i & 1) std::cout << -1 << ' ';
else std::cout << 3 << ' ';
}
std::cout << '\n';
}
C. Make it Equal
数论
题意:
给定两个多重集合\(S\)和\(T\)以及一个整数\(k\),可以在\(S\)集合上执行任意次以下操作:
- 在\(S\)中选择元素\(x\),并在\(S\)中删除\(x\)的一次出现。然后在\(S\)中插入\(x + k\)或在\(S\)中插入\(\left| x - k \right|\)判断能否通过以上操作可以得到集合\(T\)
思路:
设\(x + k, x + 2 * k, x + 3 * k\)模数的结果为\(t1\),\(\left| x - k \right|\) 模 \(k\) 为 \(t2\),发现 \(t1 + t2 = k\)的,并且 \(t1\)和\(t2\) 两类可以通过操作相互转换,所以记录集合\(S\)所有余数,然后遍历集合\(T\),看余数\(t1,t2\)够不够就可
代码:
void solve()
{
int n, k;
std::cin >> n >> k;
std::map<int,int> mp;
std::vector<int> s(n + 1),v(n + 1);
for(int i = 1; i <= n; i ++) std::cin >> s[i];
for(int i = 1; i <= n; i ++) std::cin >> v[i];
for(int i = 1; i <= n; i ++) mp[s[i] % k] ++;
for(int i = 1; i <= n; i ++)
{
if(mp[v[i] % k] > 0) mp[v[i] % k] --;
else if(mp[k - (v[i] % k)] > 0) mp[k - (v[i] % k)] --;
else
{
std::cout << "NO" << '\n';
return;
}
}
std::cout << "YES" << '\n';
}
D. Arboris Contractio
贪心
题意:
给定一棵树,可以选一个起点和一个终点然后删掉两点之间简单路径上的所有边,然后把所有路径上的结点都跟起点连一条边,问最少需要操作多少次才能使这颗树的直径最小
思路:
不难发现树最小直径只能是\(1 or 2\),当\(n = 2\)的时候最小直径就是\(1\),其他情况都是\(2\)(也就是所有点都连接同一节点也就是所谓的菊花图)。自己画几个样例会发现每一个叶子节点都需要一次操作,所以先统计所有叶子节点的个数,然后我们需要找一个合适的根节点,什么样的根节点合适呢?我们发现根节点直接相连的叶子节点是不需要操作的,所以我们要找一个叶子节点最多的节点作为根节点,最后答案就是所有根节点数量 - 与根节点相连的叶子节点
代码:
void solve()
{
int n;
std::cin >> n;
std::vector<std::vector<int>> adj(n + 1);
for(int i = 1; i < n; i ++)
{
int u,v;
std::cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u);
}
int res = 0;
for(int i = 1; i <= n; i ++)
if(adj[i].size() == 1) res ++;
if(res >= n - 1)
{
std::cout << 0 << '\n';
return;
}
int cnt = 0;
for(int i = 1; i <= n; i ++)
{
int ans = 0;
for(auto v : adj[i])
if(adj[v].size() == 1) ans ++;
cnt = std::max(cnt,ans);
}
std::cout << res - cnt << '\n';
}
E. Adjacent XOR
贪心
题意:
给定两个数组\(a,b\),对于每个\(i\)都可以进行\(a_i = a_i \oplus a_{i + 1}\)最多一次的操作,问可不可以将\(a\)转换成\(b\)
思路:
首先对于\(a_n\)如果不等于\(b_n\)的话那就一定不能,从后往前遍历,可以发现对于每个\(a_i \ne b_i\)的情况,能提供给\(a_i\)操作的数只有两个\(a_i\)和\(b_i\),所以只需维护两个数判断能不能将\(a_i\)转换成\(b_i\)即可
代码:
void solve()
{
int n;
std::cin >> n;
std::vector<int> a(n + 1),b(n + 1);
for(int i = 1; i <= n; i ++) std::cin >> a[i];
for(int i = 1; i <= n; i ++) std::cin >> b[i];
std::vector<int> num(2);
for(int i = n; i >= 1; i --)
{
if(i == n and a[i] != b[i])
{
std::cout << "NO" << '\n';
return;
}
if(a[i] == b[i])
{
num[0] = num[1] = a[i];
continue;
}
bool ok = false;
for(auto x : num)
{
if((x ^ a[i]) == b[i])
{
ok = true;
break;
}
}
if(!ok)
{
std::cout << "NO" << '\n';
return;
}
num[0] = a[i],num[1] = b[i];
}
std::cout << "YES" << '\n';
}