codeforces-div1-286-D

题目链接 Mr. Kitayuta's Colorful Graph


直接讲解法, 主要方法是分情况来做.

怎么分情况呢?

首先对于每个颜色分别处理, 处理的时候就直接用并查集, 把这种颜色能够连接起来的边搞到一个集合里面.

然后用这个颜色的并查集结果来离线更新答案, 更新的时候, 分两种情况,

1. 这个颜色的边个数大于 lim

2. 这个颜色的边小于等于 lim

PS. lim的取值会影响效率, 具体分析见下面



1. 这种情况下, 我们令ans[k]为当前第k个询问的答案, 更新答案的时候就直接遍历每个询问, 如果询问的两个节点在一个并查集内, 则这个询问的答案 + 1, 也就是ans[k] ++;


2. 这种情况下要稍微复杂一点点.

我们需要一个辅助的map <pair<int, int>, int> M;

初始化的时候遍历 每个询问 q[i][2], 令M[ make_pair(q[i][0], q[i][1]) ] = M[ make_pair(q[i][1], q[i][0]) ] = 0;

然后我们准备把这种情况下的答案保存在M中.

具体的方法是遍历这个颜色的所有边, 取出这些边相连的所有节点放到一个集合S里.

现在再遍历S中的每个节点对(a, b), 如果M.count((a, b)) == 1, 那么 M[(a, b)] ++, M[(b, a)] ++;


最后对于每个询问k, 假设他询问的是节点a和b, 那么其答案就是,  ans[k] + M[(a, b)]


效率分析

1. 这种情况下, 每次更新的复杂度是 O(Q), 其中Q是询问的个数

2. 这种情况下, 每次更新的复杂度是 O(lim*lim*log(Q));  lim*lim即是点对的个数, log(Q)是更新M的复杂度.


于是总复杂度就是  Q*A1 + lim*lim*log(Q)*A2, 其中 A1, A2分别为情况1和情况2的个数.

根据分类的定义, 可知A1 <= M / lim.


于是我们可以估计lim的值来计算各种情况下的总效率.

最后可以发现当lim在30左右的时候, 效果不错.



下面是代码

#include <stack>
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <vector>
#include <math.h>
#include <queue>
#include <map>
#include <set>
#include <algorithm>
using namespace std;

#define FOR(i, j, k) for(int i=(j);i<=(k);i++)
#define REP(i, n) for(int i=0;i<(n);i++)
#define mst(x, y) memset(x, y, sizeof(x));
#define pii pair<int, int>
#define fr first
#define sc second
#define left myleft
#define right myright
#define ll long long
#define ull unsigned long long
#define seed 1331
#define mod ((int)1e9+7)
#define eps 1e-5
#define pdd pair<double, double>

const int lim = 30;
int n, m, ans[100009];
vector <pii > E[100009], query;
map <pii, int> M;
int fa[100009];
stack <int> tmp;

int find_(int u){
    if(fa[u] == u) return u;
    return fa[u] = find_(fa[u]);
}
void merge_(int a, int b){
    int ffa = find_(a), ffb = find_(b);
    if(ffa == ffb) return ;
    fa[ffa] = ffb;
    tmp.push(ffa);
}

int main(){
//    freopen("2", "r", stdin);
    cin>>n>>m;
    FOR(i, 1, m){
        int a, b, c;
        scanf("%d %d %d", &a, &b, &c);
        E[c].push_back(pii(a, b));
    }

    int Q;
    cin>>Q;
    REP(i, Q){
        int a, b;
        scanf("%d %d", &a, &b);
        query.push_back(pii(a, b));
        M[pii(a, b)] = M[pii(b, a)] = 0;
    }

    FOR(i, 1, n) fa[i] = i;
    vector <int> id;
    FOR(i, 1, m) if(E[i].size()){
        REP(j, E[i].size()) merge_(E[i][j].fr, E[i][j].sc);

        if(E[i].size() >= lim){
            REP(k, Q) ans[k] += find_(query[k].fr) == find_(query[k].sc);
        }else{
            id.clear();
            REP(j, E[i].size()) id.push_back(E[i][j].fr), id.push_back(E[i][j].sc);
            sort(id.begin(), id.end());
            int p = unique(id.begin(), id.end()) - id.begin();
            REP(j, p) FOR(k, 0, j-1){
                int a = id[j], b = id[k];
                int t = find_(a) == find_(b);
                if(t && M.count(pii(a, b))){
                    M[pii(a, b)] ++;
                    M[pii(b, a)] ++;
                }
            }
        }

        while(!tmp.empty()){
            int x = tmp.top();
            tmp.pop();
            fa[x] = x;
        }
    }
    REP(i, Q) printf("%d\n", ans[i] + M[pii(query[i].fr, query[i].sc)]);
    return 0;
}


### Codeforces Div. 2 比赛介绍 Codeforces 是一个在线编程竞赛平台,其中Div. 2比赛面向的是那些评分低于2100分的参赛者[^1]。这类比赛旨在挑战并提升程序员解决问题的能力和技术水平。 ### 参与方式 为了参加Codeforces Div. 2的比赛,参与者首先需要注册账号,并确保个人评级满足参与条件。每次比赛前会有一个虚拟房间分配过程,在此期间选手可以选择加入特定的房间或接受随机安排。比赛通常持续两小时,其间可以尝试解决多个不同难度级别的算法问题。 ### 题目难度 Codeforces Div. 2 的题目按照难度分为几个等级,一般情况下: - **A类题**:相对容易入门级的问题,适合新手练习基础逻辑思维和编码技巧。 - **B类题**:稍微复杂一点的任务,可能涉及到更深入的数据结构应用或是简单的动态规划概念。 - **C/D类题**:这些属于中等到较难程度的问题,往往要求更高的抽象能力和创造性解法设计能力。 - **E/F及以上类别**:非常具有挑战性的高级别难题,不仅考验全面的知识体系掌握情况还涉及到了尖端的研究成果运用[^2]。 ```python # 示例代码用于展示如何连接到Codeforces API获取即将举行的赛事列表 import requests def get_upcoming_contests(): url = "https://codeforces.com/api/contest.list?gym=false" response = requests.get(url).json() upcoming_contests = [] for contest in response['result']: if 'DIV_2' in contest['name'].upper() and contest['phase'] == 'BEFORE': upcoming_contests.append(contest) return upcoming_contests ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值