Codeforces 1986 Div.3 D.E.F. 题解 --训练总结
CF 1986 D. Mathematical Problem
题目
给定一个长度为 n > 1 n > 1 n>1 的字符串 s s s ,由从 0 0 0 到 9 9 9 的数字组成。您必须在此字符串中插入恰好 n − 2 n - 2 n−2 个符号 + + + (加法)或 × \times × (乘法),才能形成有效的算术表达式。
在此问题中,符号不能放在字符串 s s s 的第一个字符之前或最后一个字符之后,并且不能连续写入两个符号。另外,请注意,字符串中数字的顺序不能更改。让我们考虑 s = 987009 s = 987009 s=987009 :
- 从此字符串可以得到以下算术表达式: 9 × 8 + 70 × 0 + 9 = 81 9 \times 8 + 70 \times 0 + 9 = 81 9×8+70×0+9=81 、 98 × 7 × 0 + 0 × 9 = 0 98 \times 7 \times 0 + 0 \times 9 = 0 98×7×0+0×9=0 、 9 + 8 + 7 + 0 + 09 = 9 + 8 + 7 + 0 + 9 = 33 9 + 8 + 7 + 0 + 09 = 9 + 8 + 7 + 0 + 9 = 33 9+8+7+0+09=9+8+7+0+9=33 。请注意,数字 09 09 09 被视为有效,并被简单地转换为 9 9 9 。
- 从此字符串无法获得以下算术表达式: + 9 × 8 × 70 + 09 +9 \times 8 \times 70 + 09 +9×8×70+09 (符号只能放在数字之间)、 98 × 70 + 0 + 9 98 \times 70 + 0 + 9 98×70+0+9 (由于有 6 6 6 个数字,因此必须恰好有 4 4 4 个符号)。
算术表达式的结果是根据数学规则计算的——首先执行所有乘法运算,然后执行加法。您需要找到通过将恰好 n − 2 n - 2 n−2 个加法或乘法符号插入给定字符串 s s s 可以获得的最小结果。
输入
每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1≤t≤104 ) — 测试用例的数量。然后是它们的描述。
每个测试用例的第一行包含一个整数 n n n ( 2 ≤ n ≤ 20 2 \leq n \leq 20 2≤n≤20 ) — 字符串 s s s 的长度。
每个测试用例的第二行包含一个长度为 n n n 的字符串 s s s ,由从 0 0 0 到 9 9 9 的数字组成。
输出
对于每个测试用例,输出通过在给定的字符串中准确插入 n − 2 n - 2 n−2 个加法或乘法符号可得到的算术表达式的最小结果。
思路
最开始看到
n
≤
20
n \leq 20
n≤20, 想的是
2
n
−
2
×
C
n
1
2^{n-2} \times C^{1}_{n}
2n−2×Cn1 枚举
n
−
1
n-1
n−1 个数中间的符号,没注意到
t
≤
1
0
4
t \leq 10^4
t≤104 , 这种多测的情况下行不通。
然后发现不考虑数字的合并,在一串数字中,
1
1
1 对答案贡献最小的方式就是
×
1
\times 1
×1, 其余数一定是
+
+
+, 因为此时有
a
+
b
≤
a
×
b
a + b \leq a \times b
a+b≤a×b.
同时,可以直接枚举合并数字的位置,一共只有 n − 1 n-1 n−1 种情况,然后计算并更新答案即可。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
int n;
string s;
int ans;
int calc(vector<int> a) {
int res = 0;
for (int i = 0; i < n - 1; i++) {
if (a[i] != 1) {
res += a[i];
}
}
return res;
}
void solve() {
ans = 1e9 + 1;
cin >> n >> s;
for (int i = 0; i < n; i++) {
if (s[i] == '0') {
if (n >= 4) {
cout << "0\n";
return;
}
else if (n == 3) {
if (i == 1) {
cout << min((s[0] - '0' + s[2] - '0'), (s[0] - '0') * (s[2] - '0')) << '\n';
return;
}
else {
cout << "0\n";
return;
}
}
else {
cout << stoi(s) << '\n';
return;
}
}
}
for (int i = 0; i < n - 1; i++) { // 第 i 个数字后不放符号
vector<int> a;
for (int j = 0; j < n; j++) {
if (j != i) {
a.push_back(s[j] - '0');
}
else {
a.push_back((s[j] - '0') * 10 + (s[j + 1] - '0'));
j++;
}
}
ans = min(ans, calc(a));
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int _T = 1;
cin >> _T;
while (_T--)
solve();
return 0;
}
CF 1986 E. Beautiful Array
题目
给定一个整数数组 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an 和一个整数 k k k 。您需要用最少的操作使它变得美观。
在应用操作之前,您可以根据需要对数组元素进行打乱。对于一个操作,您可以执行以下操作:
- 选择一个索引 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n ,
- 令 a i = a i + k a_i = a_i + k ai=ai+k 。
如果所有 1 ≤ i ≤ n 1 \leq i \leq n 1≤i≤n 都是 b i = b n − i + 1 b_i = b_{n - i + 1} bi=bn−i+1 ,则数组 b 1 , b 2 , … , b n b_1, b_2, \ldots, b_n b1,b2,…,bn 是美观的。
找到使数组变得美观所需的最少操作数,否则报告这是不可能的。
输入
每个测试由几组输入数据组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1≤t≤104 ) — 输入数据集的数量。然后是它们的描述。
每组输入数据的第一行包含两个整数 n n n 和 k k k ( 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105 , 1 ≤ k ≤ 1 0 9 1 \leq k \leq 10^9 1≤k≤109 ) — 数组 a a a 的大小和问题陈述中的数字 k k k 。
每组输入数据的第二行包含 n n n 个整数 a 1 , a 2 , … , a n a_1, a_2, \ldots, a_n a1,a2,…,an ( 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109 ) — 数组 a a a 的元素。
保证所有输入数据集的 n n n 之和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105 。
输出
对于每组输入数据,输出使数组美观所需的最少操作数,如果不可能则输出 − 1 -1 −1 。
思路
首先,相等的数必然不需要操作,成对对称放置即可;
统计每个数出现的次数,若某个数出现了奇数次,则该数或与该数配对的数必须被操作。由于每次只能
+
k
+k
+k , 所以用落单的这个数对
k
k
k 取模,加入到一个 map 中。
然后遍历 map, 显然如果某个余数对应的
v
e
c
t
o
r
.
s
i
z
e
vector.size
vector.size 是奇数,则这种情况最多出现一次。能够证明,如果
n
n
n 为偶数,那么数列只会出现偶数次上述情况;如果
n
n
n 为奇数,那么满足题意的数列只能出现这种情况一次,且将有一个数被放到数列的中间位置。
当 vector 中元素个数为偶数时,最合适的方案是相邻元素配对,对答案的贡献是
a
b
s
(
a
−
b
)
k
\frac{abs(a-b)}{k}
kabs(a−b) , 这样就不会造成重复的贡献。
出现上述情况时,以
m
=
5
,
a
,
b
,
c
,
d
,
e
m=5, a,b,c,d,e
m=5,a,b,c,d,e 为例,我们只能将奇数位置的数放到中间位置。因为如果移除偶数位置,如移除
b
b
b , 则总消费为
d
i
s
(
a
,
c
)
+
d
i
s
(
d
,
e
)
=
d
i
s
(
a
,
b
)
+
d
i
s
(
b
,
c
)
+
d
i
s
(
d
,
e
)
dis(a,c)+dis(d,e)=dis(a,b)+dis(b,c)+dis(d,e)
dis(a,c)+dis(d,e)=dis(a,b)+dis(b,c)+dis(d,e) , 明显大于移除
a
a
a 后的
d
i
s
(
b
,
c
)
+
d
i
s
(
d
,
e
)
dis(b,c)+dis(d,e)
dis(b,c)+dis(d,e)
接下来问题就转化成了 m − 1 m-1 m−1 个间隔中,选取 m − 1 2 \frac{m-1}{2} 2m−1 个间隔构成总和的最小值,且间隔不能相邻,这里用 dp 来解决。
代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
int n;
ll a[N], k;
ll dp[N][2];
ll calc(vector<ll> d, int sz) {
memset(dp, 0, sizeof(dp));
dp[0][1] = d[0];
dp[1][1] = d[1];
for (int i = 2; i < sz; i++) {
if (i % 2) {
dp[i][0] = dp[i - 1][1];
dp[i][1] = dp[i - 1][0];
}
else {
dp[i][0] = min(dp[i - 1][1], dp[i - 2][1]) + d[i + 1];
dp[i][1] = dp[i - 2][1] + d[i];
}
}
return min(dp[sz - 2][1], dp[sz - 1][1]);
}
void solve() {
map<ll, int> cnt;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
cnt[a[i]]++;
}
map<ll, vector<ll> > mp;
for (auto& [i, j] : cnt) {
if (j % 2) {
mp[i % k].push_back(i);
}
}
ll ans = 0;
bool flag = false;
for (auto& [m, v] : mp) {
int sz = v.size();
if (sz & 1) {
if (flag) {
cout << "-1\n";
return;
}
flag = true;
if (sz == 1) continue;
else if (sz == 3) {
ans += min(v[2] - v[1], v[1] - v[0]) / k;
continue;
}
else {
// 奇数个数,不能“移除”idx 为偶数的位置(从 0 开始)
// 转化为 sz-1 个间隔中,选取 (sz-1)/2 个间隔构成总和的最小值,且间隔不能相邻
vector<ll> d(sz - 1);
for (int i = 0; i < sz - 1; i++) {
d[i] = (v[i + 1] - v[i]) / k;
}
ans += calc(d, sz - 1);
}
}
else {
for (int i = 0; i < sz; i += 2) {
ans += (v[i + 1] - v[i]) / k;
}
}
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int _T = 1;
cin >> _T;
while (_T--)
solve();
return 0;
}
CF 1986 F. Non-academic Problem
题目
给定一个连通的无向图,其顶点用从 1 1 1 到 n n n 的整数编号。您的任务是最小化此图中存在路径的顶点对的数量 1 ≤ u , v ≤ n 1 \leq u,v \leq n 1≤u,v≤n 。要实现此目标,您可以从图中删除一条边。
找到最小数量的顶点对!
输入
每个测试由几组输入数据组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1≤t≤104 ) — 输入数据集的数量。然后是它们的描述。
每组输入数据的第一行包含两个整数 n n n 和 m m m ( 2 ≤ n ≤ 1 0 5 2 \leq n \leq 10^5 2≤n≤105 , n − 1 ≤ m ≤ min ( 1 0 5 , n ⋅ ( n − 1 ) 2 ) n - 1 \leq m \leq \min(10^5, \frac{n \cdot (n - 1)}{2}) n−1≤m≤min(105,2n⋅(n−1)) ) — 图中顶点的数量和边的数量。
接下来的 m m m 行中的每一行都包含两个整数 u u u 和 v v v ( 1 ≤ u , v ≤ n , u ≠ v 1 \leq u, v \leq n, u \neq v 1≤u,v≤n,u=v ),表示图中顶点 u u u 和 v v v 之间存在一条无向边。
保证给定的图是连通的,且没有多重边。
保证所有输入数据集的 n n n 与 m m m 之和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2⋅105 。
输出
对于每组输入数据,如果恰好可以移除一条边,则输出最小数量的可达顶点对。
思路
tarjan 缩点后,遍历所有的桥,求两边每个连通块中的顶点数量。
代码
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int, int>
#define x first
#define y second
using namespace std;
const int N = 1e5 + 5;
int n, m;
int cnt, dfn;
int num[N], low[N], sc[N], sz[N];
pii q[N];
vector<int> G[N], g[N];
stack<int> scc;
ll res;
void dfs(int u, int f) {
scc.push(u);
num[u] = low[u] = ++dfn;
for (int v : g[u]) {
if (v == f) continue;
if (!num[v]) {
dfs(v, u);
low[u] = min(low[u], low[v]);
}
else if (!sc[v]) {
low[u] = min(low[u], num[v]);
}
}
if (low[u] == num[u]) {
cnt++;
sz[u] = 1;
while (1) {
int v = scc.top();
scc.pop();
sc[v] = u;
if (u == v) break;
sz[u]++;
}
}
}
void tarjan() {
for (int i = 1; i <= n; i++)
if (!num[i])
dfs(i, -1);
}
void dfs1(int u, int f) {
for (int v : G[u]) {
if (v == f) continue;
dfs1(v, u);
sz[u] += sz[v];
}
for (int v : G[u]) {
if (v == f) continue;
res = max(res, (1LL * n - sz[v]) * sz[v]);
}
}
void solve() {
cin >> n >> m;
cnt = dfn = 0;
for (int i = 1; i <= n; i++) {
G[i].clear();
g[i].clear();
num[i] = low[i] = sz[i] = sc[i] = 0;
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
q[i] = {u, v};
}
tarjan();
int s = 0;
for (int i = 1; i <= m; i++) {
int u = q[i].x, v = q[i].y;
u = sc[u], v = sc[v];
s = u;
if (u != v) {
G[u].push_back(v);
G[v].push_back(u);
}
}
res = 0;
dfs1(s, 0);
cout << (n - 1LL) * n / 2 - res << '\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int _T = 1;
cin >> _T;
while (_T--)
solve();
return 0;
}