Codeforces - 1027F - Session in BSU(建图 + 并查集)

题目链接:https://codeforces.com/contest/1027/problem/F

14.1 题意

n ( 1 ≤ n ≤ 1 0 6 ) n(1 \le n \le 10^6) n(1n106) 场考试,每场考试 i i i 可以选择在第 a i a_i ai 天或者第 b i b_i bi( 1 ≤ a i < b i ≤ 1 0 9 1 \le a_i < b_i \le 10^9 1ai<bi109) 天两天中的一天参加。

现在必须参加所有考试,问最早的日期,使得截止到该日期,所有考试均已参加。

14.2 解题过程

首先对所有的日期进行离散化,之后对于每一场考试,将其所对应的两天相连边。

然后我们会发现,形成了多个连通块。考虑每一个连通块,设其点数为 n i n_i ni,边数为 m i m_i mi,则有以下几种情况:

  1. n i − 1 = m i n_i - 1 = m_i ni1=mi,此时连通块为一棵树,每条边都能找到一个点进行匹配,会多出来一个点。此情况下该连通块的答案为点集中的次大值。

  2. n i = m i n_i = m_i ni=mi,此时连通块为一基环树,每条边都能找到一个点进行匹配,没有多余的点。此情况下该连通块的答案为点集中的最大值。

  3. 其他情况下,总会有边无法成功匹配,因此遇到这种情况,会导致整个问题无解。

最终的答案为所有连通块答案的最大值。

连通块可以使用并查集维护,也可以通过 DFS 进行染色之后维护,前者时间复杂度稍高。

注意本题比较卡常,离散化时用 unordered_map 竟然会 T,改成 map 之后就过了!

时间复杂度: O ( n log ⁡ n + n α ( n ) ) O(n \log n + n \alpha (n)) O(nlogn+nα(n))

14.3 错误点

  1. 处理每个联通块的最大值和次大值,最好将该连通块中的所有点丢到一个 vector之后再进行处理。之前通过枚举边的方式来处理,很容易造成重复计算。

  2. 如果感觉使用 unordered_map 被卡常,可以考虑换成 map 再进行尝试。

  3. 并查集虽然简单,写的时候一定要仔细,很容易在某些地方写错。

14.4 代码

int n, father[maxn], rank_[maxn], sz[maxn], num[maxn];
int val[maxn][2];
void init() {
    for (int i = 0; i <= 2 * n; i++) {
        father[i] = i;
        rank_[i] = 0;
        sz[i] = 0;
        val[i][0] = val[i][1] = 0;
    }
}
int find(int x) {
    return father[x] == x ? x : father[x] = find(father[x]);
}
void merge(int x, int y) {
    x = find(x);
    y = find(y);
    if (x != y) {
        if (rank_[x] > rank_[y]) {
            father[y] = x;
        } else {
            father[x] = y;
            if (rank_[x] == rank_[y]) rank_[y]++;
        }
    }
}
int number[maxn];
map<int, int> mp;
int a[maxn], b[maxn];
vector<int> ve[maxn];
int main()
{
    scanf("%d", &n);
    init();
    int tot = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &a[i], &b[i]);
        number[++tot] = a[i];
        number[++tot] = b[i];
    }
    sort(number + 1, number + 1 + tot);
    tot = unique(number + 1, number + 1 + tot) - number - 1;
    for (int i = 1; i <= tot; i++) {
        mp[number[i]] = i;
    }
    for (int i = 1; i <= n; i++) {
        int x = mp[a[i]];
        int y = mp[b[i]];
        merge(x, y);
    }
    for (int i = 1; i <= tot; i++) {
        ve[find(i)].pb(number[i]);
    }
    for (int i = 1; i <= n; i++) {
        int x = mp[a[i]];
        int y = mp[b[i]];
        y = find(y);
        sz[y]++;
    }
    for (int i = 1; i <= tot; i++) {
        if (father[i] != i) continue;
        for (auto y: ve[i]) {
            if (y > val[i][0]) {
                val[i][1] = val[i][0];
                val[i][0] = y;
            } else if (y > val[i][1]) {
                val[i][1] = y;
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= tot; i++) {
        int y = find(i);
        if (sz[y] > ve[y].size()) return 0 * puts("-1");
        else if (sz[y] == ve[y].size()) ans = max(ans, val[y][0]);
        else ans = max(ans, val[y][1]);
    }
    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值