Network

LCA+tarjan+桥+dsu

蒟蒻刷到个难题。。。。。。

题目给的图一定是联通的。
我们大致的想法是:先进行缩点。把图变成一个树。节点与节点之间用桥连接。
这并不难,我们tarjan一下,找到桥然后染色就可以了。
然后添加边时。在书上的两个节点之间连一条边。一定会构成一个环。
这个环中原本的边会失去桥的属性。
那么:假设在u和v之间建边。u,v的最近公共祖先为LCA。
那么u->LCA v->LCA 之间的边都不在是桥。

我刚开始的代码是从u和v暴力向上找公共祖先。并一路削除路过的边的桥的属性。
如果,已经是消除过的状态无妨。如果是第一次消除,那么桥的数量就减一。

#include<iostream>
#include<algorithm>
using namespace std;
const int max_n = 1e5 + 100;
const int max_m = 2e5 + 100;
struct edge{
    int to, rev, next;
    bool vis;
}E[max_m << 1];
int head[max_n];
int cnt = 1;
void add(int from, int to, int rev, int vis = true) {
    E[cnt].to = to;E[cnt].vis = vis;
    E[cnt].next = head[from];E[cnt].rev = rev;
    head[from] = cnt++;
}

int dfn[max_n], low[max_n];
int tot = 0;
void tarjan(int u,int fa_id) {
    dfn[u] = low[u] = ++tot;
    for (int i = head[u];i;i = E[i].next) {
        int v = E[i].to;
        if (i == fa_id)continue;
        if (dfn[v] == -1) {
            tarjan(v, E[i].rev);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u])E[i].vis = E[E[i].rev].vis = false;
        }else low[u] = min(low[u], dfn[v]);
    }
}

int fa[max_n];
int cl[max_n];
int cls = 0;
int dep[max_n];
void dfs(int u, int color, int p) {
    fa[color] = p;cl[u] = color;dep[color] = dep[p] + 1;
    for (int i = head[u];i;i = E[i].next) {
        edge& e = E[i];
        if (cl[e.to] != 0)continue;
        if (e.vis == false)dfs(e.to, ++cls, color);
        else dfs(e.to, color, p);
    }
}

bool already[max_n];
int LCA(int u,int v) {
    int res = 0;
    u = cl[u];v = cl[v];
    if (dep[u] < dep[v])swap(u, v);
    while (dep[u] > dep[v]) {
        if (!already[u])++res;
        already[u] = true;
        u = fa[u];
    }while (u != v) {
        if (!already[u])++res;
        if (!already[v])++res;
        already[u] = already[v] = true;
        u = fa[u];v = fa[v];
    }return res;
}

int N, M, Q;
int main() {
    ios::sync_with_stdio(0);
    int tcase = 0;
    while (cin >> N >> M) {
        if (N == 0 && M == 0)break;

        fill(head, head + max_n, 0);cnt = 1;
        fill(dfn, dfn + max_n, -1);tot = 0;
        fill(already, already + max_n, false);
        fill(cl, cl + max_n, 0);cls = 0;

        cout << "Case " << ++tcase << ":\n";
        for (int i = 1, u, v;i <= M;++i) {
            cin >> u >> v;
            add(u, v, cnt + 1);
            add(v, u, cnt - 1);
        }tarjan(1, -1);
        dfs(1, ++cls, 0);
        int ans = 0;
        for (int i = 1;i < cnt;++i)
            if (!E[i].vis)
                ++ans;
        ans >>= 1;
        cin >> Q;
        for (int i = 1, u, v;i <= Q;++i) {
            cin >> u >> v;
            ans -= LCA(u, v);
            cout << ans << endl;
        }cout << endl;
    }
}

但,仔细想想。这种做法复杂度很高。
因为,万一我们的树退化成链表。然后Q次查询全部都是首和尾。
此时最坏的情况将是O(n*q)看下数据10^8。。。被放过了。

如何优化这个算法。利用并查集,也就是说,我们在树上也进行缩点。
将u->LCA v->LCA一路上的点都并入LCA.
同时,在找LCA时代码也有所不同了。很是巧妙。

#include<iostream>
#include<algorithm>
using namespace std;
const int max_n = 1e5 + 100;
const int max_m = 2e5 + 100;
struct edge{
    int to, rev, next;
    bool vis;
}E[max_m << 1];
int head[max_n];
int cnt = 1;
void add(int from, int to, int rev, int vis = true) {
    E[cnt].to = to;E[cnt].vis = vis;
    E[cnt].next = head[from];E[cnt].rev = rev;
    head[from] = cnt++;
}

int dfn[max_n], low[max_n];
int tot = 0;
void tarjan(int u,int fa_id) {
    dfn[u] = low[u] = ++tot;
    for (int i = head[u];i;i = E[i].next) {
        int v = E[i].to;
        if (i == fa_id)continue;
        if (dfn[v] == -1) {
            tarjan(v, E[i].rev);
            low[u] = min(low[u], low[v]);
            if (low[v] > dfn[u])E[i].vis = E[E[i].rev].vis = false;
        }else low[u] = min(low[u], dfn[v]);
    }
}

int fa[max_n];
int cl[max_n];
int cls = 0;
int dep[max_n];
void dfs(int u, int color, int p) {
    fa[color] = p;cl[u] = color;dep[color] = dep[p] + 1;
    for (int i = head[u];i;i = E[i].next) {
        edge& e = E[i];
        if (cl[e.to] != 0)continue;
        if (e.vis == false)dfs(e.to, ++cls, color);
        else dfs(e.to, color, p);
    }
}

int par[max_n];
int find(int x) { return x == par[x] ? x : par[x] = find(par[x]); }
int LCA(int u,int v) {
    int res = 0;
    u = cl[u];v = cl[v];
    int uu = find(u);int vv = find(v);
    while (uu != vv) {
        if (dep[uu] > dep[vv]) {
            par[uu] = find(fa[uu]);
            uu = find(fa[uu]);
            ++res;
        }
        else if (dep[vv] > dep[uu]) {
            par[vv] = find(fa[vv]);
            vv = find(fa[vv]);
            ++res;
        }
        else {
            par[uu] = find(fa[uu]);
            uu = find(fa[uu]);
            par[vv] = find(fa[vv]);
            vv = find(fa[vv]);
            ++res;++res;
        }
    }return res;
}

int N, M, Q;
int main() {
    ios::sync_with_stdio(0);
    int tcase = 0;
    while (cin >> N >> M) {
        if (N == 0 && M == 0)break;

        for (int i = 0;i < max_n;++i)par[i] = i;
        fill(head, head + max_n, 0);cnt = 1;
        fill(dfn, dfn + max_n, -1);tot = 0;
        fill(cl, cl + max_n, 0);cls = 0;

        cout << "Case " << ++tcase << ":\n";
        for (int i = 1, u, v;i <= M;++i) {
            cin >> u >> v;
            add(u, v, cnt + 1);
            add(v, u, cnt - 1);
        }tarjan(1, -1);
        dfs(1, ++cls, 0);
        int ans = 0;
        for (int i = 1;i < cnt;++i)
            if (!E[i].vis)
                ++ans;
        ans >>= 1;
        cin >> Q;
        for (int i = 1, u, v;i <= Q;++i) {
            cin >> u >> v;
            ans -= LCA(u, v);
            cout << ans << endl;
        }cout << endl;
    }
}
kuangbin刷题题单详解(连通图) 文章被收录于专栏

题单:https://vjudge.net/article/371

全部评论

相关推荐

一共一个小时,面试难度以及自己的回答算是最近的面试压力比较大的,实习问了30分钟,中间穿插八股。1.redis数据结构2.redis持久化机制3.mysql索引底层4.聚簇索引与非聚簇索引5.索引优化6.索引失效7.mysql执行一条sql8.那么多索引mysql怎么选(不会)9.tcp与udp区别10.tcp为什么可靠11.消息队列作用12.kafka怎么保证消息有序性13.mcp是什么?14.skills是什么?15.jvm内存分配与回收过程(我讲了从创建对象到判断垃圾对象到垃圾回收我全说了一遍,是这个吗?)16.fullgc触发机制17.tcp的拥塞控制流程(不会了)18.分布式事务解决方案,说了2pc,3pc,tcc。算法是反转双向链表,没有按格式输出,但是面试官没让继续写了,面完以为挂了,结果晚上秒过,看看复试什么情况吧。今天百度打电话准备发offer了,业务跟在手子的差不多,很垂,并且说不分日常暑期,只看表现,会有转正机会,但是考虑再三还是拒绝了,百度实习薪资确实有点低,title也不如之前了,但是面试的二位业务老师我很喜欢,对我的评价也不错,希望之后能有机会共事。从三月份到现在一共面了六家,面试次数总共是8场,情况如下:脉脉二面(无答复,默认挂)百度二面已oc美团一面过,下周一二面shein一面过直接HR面游族一面过直接HR面腾讯一面过等待约二面滴滴明天一面面试通过率还是蛮高的,但是大部分都是日常,感觉对我现在的加成不大,大概率不会去,不知道暑期会是什么情况呢唉,希望能有面试吧,继续加油。字节被无hc直接取消了,现在还没人捞,有没有字节HR救救我
不管什么都不想跳动了:本人美团百度快手都待过,建议肯定是直接留快手多一点产出后转正or直接冲字节腾讯暑期吧。一是快手从福利到基建都吊打另外两家。美团现在这个业务比较惨,本来毛利就很低,亏损严重,今年很可能要优化人力降低成本,去了别说日常,就算暑期后面都很可能被优化。百度其实实习生权限挺高的,可以接触到一些含金量高的项目,但是现在的风评不如之前了,薪资也不高。二是转正概率和薪资是跟产出挂钩的,你都在手子已经积累产出了,去其他家日常实习产出都是从0开始,肯定不可能有你在手子转正可能性大啊,现在日常压根没必要去,而且我有两个师弟都是在快手日常转正的,不用太担心,安心留在手子一边多做一点产出然后一边冲字节腾讯暑期,字节腾讯今年实习岗位非常多的,不如好好把握这个,加油。
查看18道真题和解析
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务