Codeforces 1986 Div.3 D.E.F. 题解 --训练总结

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 n2 个符号 + + + (加法)或 × \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 n2 个加法或乘法符号插入给定字符串 s s s 可以获得的最小结果。

输入

每个测试由多个测试用例组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1t104 ) — 测试用例的数量。然后是它们的描述。

每个测试用例的第一行包含一个整数 n n n ( 2 ≤ n ≤ 20 2 \leq n \leq 20 2n20 ) — 字符串 s s s 的长度。

每个测试用例的第二行包含一个长度为 n n n 的字符串 s s s ,由从 0 0 0 9 9 9 的数字组成。

输出

对于每个测试用例,输出通过在给定的字符串中准确插入 n − 2 n - 2 n2 个加法或乘法符号可得到的算术表达式的最小结果。

思路

最开始看到 n ≤ 20 n \leq 20 n20, 想的是 2 n − 2 × C n 1 2^{n-2} \times C^{1}_{n} 2n2×Cn1 枚举 n − 1 n-1 n1 个数中间的符号,没注意到 t ≤ 1 0 4 t \leq 10^4 t104 , 这种多测的情况下行不通。
然后发现不考虑数字的合并,在一串数字中, 1 1 1 对答案贡献最小的方式就是 × 1 \times 1 ×1, 其余数一定是 + + +, 因为此时有 a + b ≤ a × b a + b \leq a \times b a+ba×b.

同时,可以直接枚举合并数字的位置,一共只有 n − 1 n-1 n1 种情况,然后计算并更新答案即可。

代码

#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 1in
  • a i = a i + k a_i = a_i + k ai=ai+k

如果所有 1 ≤ i ≤ n 1 \leq i \leq n 1in 都是 b i = b n − i + 1 b_i = b_{n - i + 1} bi=bni+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 1t104 ) — 输入数据集的数量。然后是它们的描述。

每组输入数据的第一行包含两个整数 n n n k k k ( 1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1n105 , 1 ≤ k ≤ 1 0 9 1 \leq k \leq 10^9 1k109 ) — 数组 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 1ai109 ) — 数组 a a a 的元素。

保证所有输入数据集的 n n n 之和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2105

输出

对于每组输入数据,输出使数组美观所需的最少操作数,如果不可能则输出 − 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(ab) , 这样就不会造成重复的贡献。
出现上述情况时,以 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 m1 个间隔中,选取 m − 1 2 \frac{m-1}{2} 2m1 个间隔构成总和的最小值,且间隔不能相邻,这里用 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 1u,vn 。要实现此目标,您可以从图中删除一条边。

找到最小数量的顶点对!

输入

每个测试由几组输入数据组成。第一行包含一个整数 t t t ( 1 ≤ t ≤ 1 0 4 1 \leq t \leq 10^4 1t104 ) — 输入数据集的数量。然后是它们的描述。

每组输入数据的第一行包含两个整数 n n n m m m ( 2 ≤ n ≤ 1 0 5 2 \leq n \leq 10^5 2n105 , 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}) n1mmin(105,2n(n1)) ) — 图中顶点的数量和边的数量。

接下来的 m m m 行中的每一行都包含两个整数 u u u v v v ( 1 ≤ u , v ≤ n , u ≠ v 1 \leq u, v \leq n, u \neq v 1u,vn,u=v ),表示图中顶点 u u u v v v 之间存在一条无向边。

保证给定的图是连通的,且没有多重边。

保证所有输入数据集的 n n n m m m 之和不超过 2 ⋅ 1 0 5 2 \cdot 10^5 2105

输出

对于每组输入数据,如果恰好可以移除一条边,则输出最小数量的可达顶点对。

思路

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;
}
  • 13
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值