Martian148's blog Martian148's blog
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档

Martian148

一只热爱文科的理科生
首页
  • ICPC 算法笔记
  • ICPC 算法题解
  • 体系结构
  • 高等数学
  • 线性代数
  • 概率论与数理统计
  • 具体数学
  • Martian148的奇思妙想
  • 游记
  • 通识课笔记
关于
  • useful 网站
  • 友情链接
  • 分类
  • 归档
  • codeforses 题解

  • atcoder 题解

  • XCPC 题解

    • ICPC2023 合肥区域赛 题解
      • E - Matrix Distances
        • Question
        • Solution
        • Code
      • F - Colorful Balloons
        • Solution
        • Code
      • G - Streak Manipulation
        • Question
        • Solution
        • Code
      • J - Takeout Delivering
        • Question
        • Solution
        • Code
        • summary
    • ICPC2024 武汉邀请赛 题解
    • ICPC2024 江西省赛 题解
    • ICPC2024 网络赛 第一场 题解
    • CCPC2023 哈尔滨站 题解
    • CCPC2023 桂林站 题解
    • CCPC2023 秦皇岛站 题解
    • CCPC2024 哈尔滨站 题解
  • 校训练赛题解

  • 牛客题解

  • 蓝桥杯题解

  • 典题选讲

  • 杂题

  • 算法题解
  • XCPC 题解
martian148
2024-08-25
目录

ICPC2023 合肥区域赛 题解

The 2023 ICPC Asia Hefei Regional Contest (The 2nd Universal Cup. Stage 12: Hefei) (opens new window)

# E - Matrix Distances

# Question

有一个大小为 n×mn \times mn×m 的矩阵,每个单元格填充了颜色值 ci,jc_{i,j}ci,j​。计算所有具有相同颜色的单元格对之间曼哈顿距离的总和。

具体而言,请计算:

Total Sum=∑C∑(xi,yi)∈SC∑(xj,yj)∈SC∣xi−xj∣+∣yi−yj∣ \text{Total Sum} = \sum_C \sum_{(x_i, y_i) \in S_C} \sum_{(x_j, y_j)\in S_C} \left|x_i-x_j\right|+\left|y_i-y_j\right| Total Sum=C∑​(xi​,yi​)∈SC​∑​(xj​,yj​)∈SC​∑​∣xi​−xj​∣+∣yi​−yj​∣

这里,CCC 表示所有不同颜色的集合。SCS_CSC​ 表示颜色等于指定颜色 CCC 的坐标集合 (x,y)(x, y)(x,y)。

# Solution

很显然,需要一个一个颜色计算,对于一个颜色 ccc 发现 x,yx,yx,y 对答案的贡献是独立的,所以可以拆开来计算

考虑如何计算 xxx 对答案的贡献

先将 xxx 从小到大排序,计算前缀和数组 sss,当枚举到一个 xix_ixi​,有 i−1i-1i−1 个 xxx 比当前的 xxx 小,对答案的贡献就是 xi×i−six_i\times i-s_ixi​×i−si​,同时,有 m−im-im−i 个 xxx 比当前的 xix_ixi​ 大,对答案的贡献就是 si−xi×(m−i)s_i-x_i\times(m-i)si​−xi​×(m−i),其中 mmm 表示当前颜色方块的个数

yyy 和 xxx 的计算方法相同

# Code

#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;

int main() {
    ios::sync_with_stdio(false);
    int n, m; cin >> n >> m;
    vector<vector<int>> a(n + 1, vector<int>(m + 1));
    vector<int> b;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> a[i][j];
            b.push_back(a[i][j]);
        }
    }
    sort(b.begin(), b.end());
    int cnt = unique(b.begin(), b.end()) - b.begin();
    vector<vector<int>> X(cnt + 1), Y(cnt + 1);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            a[i][j] = lower_bound(b.begin(), b.begin() + cnt, a[i][j]) - b.begin() + 1;
            X[a[i][j]].push_back(i);
            Y[a[i][j]].push_back(j);
        }
    }
    long long ans = 0;
    auto calc = [&] (vector<int>& v) {
        int n = v.size();
        if (n == 0) return 0ll;
        vector<long long> sum(n);
        sort(v.begin(), v.end());
        sum[0] = v[0];
        for (int i = 1; i < n; i++) sum[i] = sum[i - 1] + v[i];
        long long ret = 0;
        for (int i = 0; i < n; i++) {
            ret += 1ll * v[i] * (i + 1) - sum[i];
            ret += sum[n - 1] - sum[i] - 1ll * v[i] * (n - (i + 1));
        }
        return ret;
    };

    for (auto v : X) ans += calc(v);
    for (auto v : Y) ans += calc(v);
    cout << ans << '\n';
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# F - Colorful Balloons

# Solution

签到题

# Code

#include<bits/stdc++.h>
using namespace std;
int n, mx;
string s, ans;
map<string, int> mp;
int main(){
    ios::sync_with_stdio(false);
    cin >> n;
    for(int i = 1; i <= n; i ++){
        cin >> s;
        mp[s] ++;
        if(mx < mp[s]){
            mx = mp[s];
            ans = s;
        }
    }
    if(mx > n / 2) cout << ans << endl;
    else cout << "uh-oh" << endl;
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# G - Streak Manipulation

# Question

给定一个 01 串,长度为 nnn,最多可以操作 mmm 次,每次操作把一个 000 变成 111,求不超过 mmm 次操作后,第 kkk 长的连续为 111 的串的长度

# Solution

考虑二分答案 midmidmid,即在最多 mmm 次操作之后,第 kkk 长的串的大小为 midmidmid

考虑如何 check,注意到 kkk 其实很小,可以被定义到动态规划的状态里面去

定义 dp[i][j][0/1]dp[i][j][0/1]dp[i][j][0/1] 表示:前 iii 个字符,有 jjj 段长度大于等于 midmidmid 的连续 111 串,且当前字符是否位于第 jjj 段连续 111 串的末尾,所需的最少操作次数

考虑状态转移

  • s[i]=0s[i]=0s[i]=0
    • dp[i][j][0]=min⁡(dp[i][j][0],dp[i−1][j][0],dp[i−1][j][1])dp[i][j][0]=\min(dp[i][j][0],dp[i-1][j][0],dp[i-1][j][1])dp[i][j][0]=min(dp[i][j][0],dp[i−1][j][0],dp[i−1][j][1])
    • dp[i][j][1]=min⁡(dp[i][j][1],dp[i−1][j][1]+1)dp[i][j][1]=\min(dp[i][j][1],dp[i-1][j][1]+1)dp[i][j][1]=min(dp[i][j][1],dp[i−1][j][1]+1)
  • s[i]=1s[i]=1s[i]=1
    • dp[i][j][0]=min⁡(dp[i][j][0],dp[i][j][0])dp[i][j][0]=\min(dp[i][j][0],dp[i][j][0])dp[i][j][0]=min(dp[i][j][0],dp[i][j][0])
    • dp[i][j][1]=min⁡(dp[i][j][1],dp[i−1][j][1])dp[i][j][1]=\min(dp[i][j][1],dp[i-1][j][1])dp[i][j][1]=min(dp[i][j][1],dp[i−1][j][1])
  • 此外,在 i≥midi\ge midi≥mid 且 j>0j>0j>0 时
    • dp[i][j][1]=min⁡(dp[i][j][1],dp[i−mid][j−1][0]+∑k=i−mid+1i[s[k]=0])dp[i][j][1]=\min(dp[i][j][1],dp[i-mid][j-1][0]+\sum_{k=i-mid+1}^i [s[k]=0])dp[i][j][1]=min(dp[i][j][1],dp[i−mid][j−1][0]+∑k=i−mid+1i​[s[k]=0])

其中,∑k=i−mid+1i[s[k]=0]\sum_{k=i-mid+1}^i [s[k]=0]∑k=i−mid+1i​[s[k]=0] 可以用前缀和数组快速求出

总时间复杂度为 O(knlog⁡n)O(kn\log n)O(knlogn)

# Code

#include <bits/stdc++.h>
using namespace std;
constexpr int INF = 0x3f3f3f3f;
const int MAXN = 2e5 + 5;
int dp[MAXN][6][2];

int main() {
    ios::sync_with_stdio(false);
    int n, m, k; cin >> n >> m >> k;
    string s; cin >> s; s = " " + s;
    vector<int> pre(n + 1, 0);
    for (int i = 1; i <= n; i++) 
        pre[i] = pre[i - 1] + (s[i] == '0');
    
    int l = 0, r = n + 1;

    auto check = [&] (int x) {
        for (int i = 0; i <= n; i++)
            for (int j = 0; j <= k; j++)
                dp[i][j][0] = dp[i][j][1] = INF;

        dp[0][0][0] = 0;
        for (int i = 1; i <= n; i++)
            for (int j = 0; j <= k; j++) {
                if (s[i] == '0') {
                    dp[i][j][0] = min({dp[i][j][0], dp[i - 1][j][0], dp[i - 1][j][1]});
                    dp[i][j][1] = min({dp[i][j][1], dp[i - 1][j][1] + 1});
                }
                else {
                    dp[i][j][0] = min({dp[i][j][0], dp[i - 1][j][0]});
                    dp[i][j][1] = min({dp[i][j][1], dp[i - 1][j][1]});
                }
                if (i >= x && j > 0) 
                    dp[i][j][1] = min({dp[i][j][1], dp[i - x][j - 1][0] + pre[i] - pre[i - x]});
            }
        return min(dp[n][k][0], dp[n][k][1]) <= m;
    };

    while (l + 1 < r) {
        int mid = (l + r) >> 1;
        if (check (mid)) l = mid;
        else r = mid;
    }
    if (l <= 0) cout << -1 << '\n';
    else cout << l << '\n';
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# J - Takeout Delivering

# Question

给你一个无向连通图,定义最短路为路径中边权最大的两条边的边权和,求 111 到 nnn 的最短路

  • 2≤n≤3×105,m≤1062\le n\le 3\times 10^5,m\le 10^62≤n≤3×105,m≤106

# Solution

当时考虑了很久二分,后来发现方向错了

我们需要求一条路径上的路径最大值和次大值的和

那么我们枚举每一条边,假设这条边就是路径上的最大值,那么次大值就应该出现在从 1∼u,v∼n1\sim u,v\sim n1∼u,v∼n 或者 1∼v,u∼n1\sim v,u\sim n1∼v,u∼n

提前用 dijkstra 提前预处理出 1∼x1\sim x1∼x 的最小路径最大值,同理也能处理出 x∼nx\sim nx∼n 的最小路径最大值

# Code

#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9 + 10;
int main() {
    freopen ("J.in", "r", stdin);
    ios::sync_with_stdio(false);
    int n, m; cin >> n >> m;
    vector<vector<pair<int, int>>> g(n + 1);
    for (int i = 1; i <= m; i++) {
        int u, v, w; cin >> u >> v >> w;
        g[u].push_back({v, w});
        g[v].push_back({u, w});
    }

    auto dijkstra = [&] (int s) {
        vector<int> dis(n + 1, INF);
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
        vector<int> vis(n + 1, 0);
        dis[s] = 0;
        pq.push({0, s});
        while (!pq.empty()) {
            auto [d, u] = pq.top(); pq.pop();
            if (vis[u]) continue;
            vis[u] = 1;
            for (auto [v, w] : g[u]) {
                int tmp = max(dis[u], w);
                if (tmp < dis[v]) {
                    dis[v] = tmp;
                    pq.push({dis[v], v});
                }
            }
        }
        return dis;
    };

    auto dis1 = dijkstra(1);
    auto disn = dijkstra(n);

    int ans = INF + INF;
    
    for (int u = 1; u <= n; u++) {
        for (auto [v, w] : g[u]) {
            if (w >= max(dis1[u], disn[v]))
                ans = min(ans, max(dis1[u], disn[v]) + w);
            if (w >= max(dis1[v], disn[u]))
                ans = min(ans, max(dis1[v], disn[u]) + w);
        }
    }
    cout << ans << '\n';
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# summary

对于这种需要维护一个最大值和一个次大值

  1. 考虑开一个 pair<int,int> 或者什么结构,使用最大值和次大值只增不减的特性
  2. 考虑二分其中一个
  3. 考虑枚举其中一个,看能否快速求出另外一个
上次更新: 2025/04/08, 18:03:31
AtCoder Beginner Contest 369 A-F 题解
ICPC2024 武汉邀请赛 题解

← AtCoder Beginner Contest 369 A-F 题解 ICPC2024 武汉邀请赛 题解→

最近更新
01
Java基础语法
05-26
02
开发环境配置
05-26
03
pink 老师 JavaScript 学习笔记
05-26
更多文章>
Theme by Vdoing | Copyright © 2024-2025 Martian148 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式