比赛链接
A
题意
\(a,b\,每次操作可以使 \(a\ 加上 \(+1,+2,+5,-1,-2,-5\ 中的一个数,求最少多少次操作可以将 \(a\ 变成 \(b\ 。
题解
知识点:贪心。
\(5\ 后 \(2\ 再 \(1\ 。
考虑三个硬币 \(a>b>c\,令 \(a = kb+mc,b = nc\ 。
\(n - m \leq k\ 则任意数量的 \(a\ 都不可替代。
\(n - i(m-1-1 \leq ik,i \geq 1\,则一次不能替代大于等于 \(i\ 个 \(a\,但可以用 \(jk+1\ 个 \(b\ 替代 \(j\ 个 \(a\ 和 \(n-jm\ 个 \(c\,其中 \(j<i\ 。
\(O(1\
\(O(1\
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve( {
int a, b;
cin >> a >> b;
int ans = abs(b - a / 5 + abs(b - a % 5 / 2 + abs(b - a % 5 % 2;
cout << ans << '\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\ 个冰箱,\(m\ 个锁链,每个冰箱的价值为 \(a_i\ 。一个锁链可以连接两个冰箱,这两个冰箱就会被这条锁链锁住,而设置这条锁链的花费是连接的两个冰箱的价值之和。
题解
知识点:贪心,构造。
\(2\,考虑能否达成这个最小值。
\(1 \to 2,2\to3,\cdots,n-1\to n,n \to 1\,而且用的锁链最少,为 \(n\ 条。
\(n=2\ 或者 \(m<n\ 的情况,分别是不能构成环或锁链不够,那么是无解的。
\(O(n\
\(O(1\
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
bool solve( {
int n, m;
cin >> n >> m;
int ans = 0;
for (int i = 1;i <= n;i++ {
int x;
cin >> x;
ans += x;
}
if (n == 2 || m < n return false;
cout << 2 * ans << '\n';
for (int i = 1;i < n;i++ {
cout << i << ' ' << i + 1 << '\n';
}
cout << n << ' ' << 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;
}
C
题意
有一个长为 \(n\ 的排列 \(p\,生成一个长为 \(n-2\ 的三元组序列 \(q\,其中 \(q_i = (p_i,p_{i+1},p_{i+2}\ 。
\(q\,即每个三元组内部被打乱,\(q\ 的排序也被打乱,求符合 \(q\ 的一个 \(p\ 。
题解
知识点:构造。
\(p\ 生成的 \(q\ 中,数字出现的次数是有规律的。 \(p_1,p_n\ 恰好出现一次,\(p_2,p_{n-1}\ 恰好出现两次,其余元素都会恰好出现三次。通过这个性质我们就能初步断定出现一次的一定在首或尾,出现两次的一定在第二个或倒数第二个,其余元素不确定。
\(p_1\,那么 \(p_1\ 所在元组就没用了,我们把与 \(p_1\ 在一个元组里的数的次数减一,此时出现新的出现次数为 \(1\ 的数,他就是紧接着的第二个数,以此类推取数即可。
\(5\ 个数的时候呈现 \(1,2,3,2,1\,取走一个 \(1\ 的数以后呈现 \(1,2,2,1\,再取一次就变成 \(1,1,1\,出现了三个 \(1\ 没法判断了,因此,我们要在一开始确定 \(p_1\ 的时候,直接把 \(p_{n-1},p_{n}\ 都确定了,这样到最后三个不确定的数中已经有两个确定过了就可以直接判断了。于是,我们开一个数组 \(vis\ 记录数有没有被取过,一开始把 \(p_{n-1},p_n\ 取走就行。
\(O(n\
\(O(n\
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int cnt[100007];
vector<int> g[100007];
bool vis[100007];
int main( {
std::ios::sync_with_stdio(0, cin.tie(0, cout.tie(0;
int n;
cin >> n;
for (int i = 1;i <= n - 2;i++ {
int u, v, w;
cin >> u >> v >> w;
cnt[u]++;
cnt[v]++;
cnt[w]++;
g[u].push_back(v;
g[v].push_back(u;
g[u].push_back(w;
g[w].push_back(u;
g[v].push_back(w;
g[w].push_back(v;
}
vector<int> st;
for (int i = 1;i <= n;i++ {
if (cnt[i] == 1 st.push_back(i;
}
int fst = st[0];
int lst = st[1];
vis[fst] = vis[lst] = 1;
int lst2;
for (auto v : g[lst] {
if (cnt[v] == 2 {
vis[v] = 1;
lst2 = v;
break;
}
}
for (int u = fst, w = 0;u;u = w {
w = 0;
cout << u << ' ';
for (auto v : g[u] {
if (vis[v] continue;
cnt[v]--;
if (cnt[v] == 1 w = v;
}
}
cout << lst2 << ' ' << lst << '\n';
return 0;
}
D
题意
给一张 \(r \times c\ 的地图,地图上有米 R
和空地 .
。
\(k\ 只鸡,鸡的行走规则是只能从一格走到相邻的四格,地图上的格子鸡都能走。
\(1\ 个区域,鸡只能在自己的区域里走,行走规则不变,要求每个鸡能吃到米的数量中的极差(最大值与最小值的差)最小。
题解
知识点:贪心,数学。
设总米数为 \(sum\,考虑给每只鸡先分配 \(\left\lfloor \dfrac{sum}{k} \right\rfloor\ 个米,多出来的 \(sum \bmod k\ 个米一人一个分配完,这样极差最小。
\(O(rc\
\(O(rc\
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
char dt[107][107];
bool solve( {
int r, c, k;
cin >> r >> c >> k;
int sum = 0;
for (int i = 1;i <= r;i++ {
for (int j = 1;j <= c;j++ {
cin >> dt[i][j];
if (dt[i][j] == 'R' sum++;
}
}
int avg = sum / k;
int rst = sum % k;
int pos = 0, cnt = 0;
for (int i = 1;i <= r;i++ {
if (i & 1 {
for (int j = 1;j <= c;j++ {
if (dt[i][j] == 'R' cnt++;
if (cnt > avg + (rst > 0 {
cnt = 1;
pos++;
if (rst > 0 rst--;
}
if (pos < 10 dt[i][j] = pos + '0';
else if (10 <= pos && pos < 36 dt[i][j] = pos - 10 + 'A';
else dt[i][j] = pos - 36 + 'a';
}
}
else {
for (int j = c;j >= 1;j-- {
if (dt[i][j] == 'R' cnt++;
if (cnt > avg + (rst > 0 {
cnt = 1;
pos++;
if (rst > 0 rst--;
}
if (pos < 10 dt[i][j] = pos + '0';
else if (10 <= pos && pos < 36 dt[i][j] = pos - 10 + 'A';
else dt[i][j] = pos - 36 + 'a';
}
}
}
for (int i = 1;i <= r;i++ {
for (int j = 1;j <= c;j++ {
cout << dt[i][j];
}
cout << '\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
题意
给 \(n\ 个数 \(a_i\,每次操作可以任选一个数 \(a_i\ 减 \(1\ 并在 \(a_{i-1},a_{i+1}\ 中选择一个加 \(1\ (如果存在的话),即 \(a_i\ 给它的一个邻居一个 \(1\ 。
\(k>1\ 能整除所有数。
Easy版 \(n \in [1,10^5],a_i \in \{ 0,1\}\
Hard版 \(n \in [1,10^6],a_i \in [1,10^6]\
题解
知识点:质因数分解,贪心,枚举。
\(k\ 能整除 \(a_i\,等价于 \(k\ 能整除 \(sum = \sum a_i\,而总和是不变的,因此我们可以直接通过 \(sum\ 的因子求出 \(k\ 的可行值来枚举。
\(10^{12}\ 的数,枚举他的全部因子的复杂度是不可行的。实际上,并不是所有 \(k\ 都需要枚举的,我们只需要枚举 \(sum\ 的质因子 \(k\ 即可,因为对于一个合数因子的答案,他的质因子的答案不会更坏。质因子最多 \(12\ 个,复杂度完全可以接受。所以,我们先处理出 \(sum\ 的质因子 \(pfactor\ 数组,随后遍历求出操作取最小值。
\(k\,我们希望得到修改 \(a_i\ 数组的最小操作次数。我们可以从左往右遍历,假设前 \(i-1\ 个都已经能被整除,那么 \(a_i\ 没有必要对 \(a_{i-1}\ 操作了,只可能把 \(a_i \bmod k\ 个 \(1\ 给 \(a_{i+1}\ 或者让 \(a_{i+1}\ 给自己 \(k - a_i \bmod k\ 个 \(1\,从中取最小值就是 \(a_i\ 需要的操作。
\(i-1\ 能被 \(k\ 整除后,前 \(i-1\ 个数的操作最后都积累到 \(i\ 身上了,因此对于 \(a_i\,它已经变成 \((\sum_{j=1}^i a_i \bmod k\ 。因此,令 \((\sum_{j=1}^i a_i \bmod k = pre_i\,我们每次取最小值时,实际上是取 \(\min (pre_i,k-pre_i\ 。
\(sum = 1\ 的情况无解。
我们可以直接枚举因子 \(k\,总数是 \(100\ 量级。
\(k\ 求最小操作时,可以直接从 \(1\ 到 \(n\ 贪心地每 \(k\ 个 \(1\ 分一个区域,其他分法不会更优,每个区域的操作最小值是 \(k\ 个数到第 \(\left\lceil \dfrac{k}{2}\right\rceil\ 个数的距离总和(因为 \(a_i\ 都是 \(1\,代价等价于距离)。证明方法:假设 \(k\ 个数从小到大排序好了,可以设 \(f(x\ 为选择到第 \(x\ 个数的距离总和,通过作差 \(f(x+1-f(x\ 发现是一个单谷函数,极值点在 \(x = \left\lceil \dfrac{k}{2}\right\rceil\ 。
\(O(n+\sqrt{\sum a_i}\
\(O(n\
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int a[1000007];
void get_pfactor(ll n, vector<ll> &pfactor {
for (int i = 2;1LL * i * i <= n;i++ {
if (n % i == 0 {
pfactor.push_back(i;
while (n % i == 0 n /= i;
}
}
if (n > 1 pfactor.push_back(n;
}
int main( {
std::ios::sync_with_stdio(0, cin.tie(0, cout.tie(0;
int n;
cin >> n;
ll sum = 0;
for (int i = 1;i <= n;i++ cin >> a[i], sum += a[i];
if (sum == 1 {
cout << -1 << '\n';
return 0;
}
vector<ll> pfactor;
get_pfactor(sum, pfactor;
ll ans = 1e18;
for (auto p : pfactor {
ll pre = 0, cnt = 0;
for (int i = 1;i <= n;i++ {
(pre += a[i] %= p;
cnt += min(pre, p - pre;
}
ans = min(ans, cnt;
}
cout << ans << '\n';
return 0;
}