zoukankan      html  css  js  c++  java
  • tarjan学习笔记

    1.$tarjan$求强连通分量

    思想:在$dfs$的过程中,把强连通分量中的点入栈,当找到一个强连通分量的最起始的点,就将其所在强连通分量中的点出栈。

    缩点

    把强连通分量中的点缩成一个点,进行重新建图,从而解决一些问题。

    2.割点
    若将这个点在图中所连的边删去,图变得不连通,则称这个点为一个割点。

    考虑两种情况:

    若节点$x$为根节点,则它若联结着两颗及以上数量的子树,则$x$为割点。

    $otherwise$,设$x$的其中一个儿子为$v$,若出现$low[v] ge dfn[x]$,则$x$为割点。

    3.习题

    luogu 3388 【模板】割点(割顶)

    #include <bits/stdc++.h>
    const int MAXN = 20050;
    const int MAXM = 100050;
    using namespace std;
    struct node {
        int to, nextt;
    }edge[MAXM << 1];
    int n, m, u, v, num, cnt, low[MAXN], dfn[MAXN], head[MAXN];
    set<int> s;
    void addedge(int u, int v) {
        edge[++num].to = v;
        edge[num].nextt = head[u];
        head[u] = num;
    }
    void tarjan(int x, int fa) {
        dfn[x] = low[x] = ++cnt;
        int child = 0;
        for(int i = head[x]; i; i = edge[i].nextt) {
            int to = edge[i].to;
            if(!dfn[to]) {
                if(x == fa)
                    child++;
                tarjan(to, x);
                low[x] = min(low[x], low[to]);
                if(x != fa && low[to] >= dfn[x])
                    s.insert(x);
            }
            low[x] = min(low[x], dfn[to]);
        }
        if(x == fa && child >= 2)
            s.insert(x);
    }
    int main() {
        cin >> n >> m;
        for(int i = 1; i <= m; i++) {
            cin >> u >> v;
            addedge(u, v);
            addedge(v, u);
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i, i);
        }
        cout << (int)s.size() << endl;
        for(set<int>::iterator it = s.begin(); it != s.end(); it++)
            cout << *it << " ";
        cout << endl;
        return 0;
    }

    Luogu 2921 Trick or Treat on the Farm

    第一种做法是进行$tarjan$,对于所在强连通分量里个数大于$1$的点,答案即为当前强连通分量的大小;由于数据的特殊性,另一种情况即为到强连通分量的距离加上那个强连通分量的大小。

    第二种做法是三遍递归。第一遍把所有不在环内的点打上标记,第二遍求出未被标记的点所在环的大小,第三遍累加到环的距离。总体想法与第一种是一样的。

    代码(#2):

    #include <bits/stdc++.h>
    const int MAXN = 100050;
    using namespace std;
    int n, x, nextt[MAXN], ans[MAXN], in[MAXN];
    bool vis[MAXN];
    void dfs1(int x) {
        vis[x] = true;
        if(!--in[nextt[x]])
            dfs1(nextt[x]);
    }
    int dfs2(int x, int num) {
        vis[x] = true;
        ans[x] = num;
        if(ans[nextt[x]])    
            return num;
        return ans[x] = dfs2(nextt[x], num + 1);
    }
    int dfs3(int x) {
        if(ans[x])
            return ans[x];
        return ans[x] = dfs3(nextt[x]) + 1;
    }
    int main() {
        cin >> n;
        for(int i = 1; i <= n; i++) {
            cin >> nextt[i];
            ++in[nextt[i]];
        }
        for(int i = 1; i <= n; i++) {
            if(!vis[i] && !in[i])
                dfs1(i);
        }
        for(int i = 1; i <= n; i++) {
            if(!ans[i] && in[i])
                x = dfs2(i, 1);
        }
        for(int i = 1; i <= n; i++) {
            if(!ans[i] && !in[i])
                x = dfs3(i);
        }
        for(int i = 1; i <= n; i++)
            cout << ans[i] << endl;
        return 0;
    }

    Luogu 1726 上白泽慧音

    显然答案即为最大的强连通分量的个数。

    #include <bits/stdc++.h>
    const int MAXN = 5050;
    const int MAXM = 50050;
    const int INF = 1 << 30;
    using namespace std;
    stack<int> s;
    struct Edge {
        int to , nextt;
    }edge[MAXM << 1];
    int n, m, u, v, ty, num, cnt, cnt1, Max, pos, ss[MAXN], head[MAXN], low[MAXN], dfn[MAXN], sccno[MAXN];
    void addedge(int u, int v) {
        edge[++num].to = v;
        edge[num].nextt = head[u];
        head[u] = num;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        s.push(x);
        for(int i = head[x]; i; i = edge[i].nextt) {
            int to = edge[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(dfn[x] == low[x]) {
            int now, cnt2 = 0;
            ++cnt1;
            while(true) {
                now = s.top();
                s.pop();
                ++cnt2;
                sccno[now] = cnt1;
                ss[now] = cnt1;
                if(now == x)
                    break;
            }
            if(cnt2 > Max) {
                Max = cnt2;
                pos = cnt1;
            }
        }
    }
    int main() {
        cin >> n >> m;
        for(int i = 1; i <= m; i++) {
            cin >> u >> v >> ty;
            addedge(u, v);
            if(ty == 2)
                addedge(v, u);
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i);
        }
        cout << Max << endl;
        for(int i = 1; i <= n; i++) {
            if(ss[i] == pos) {
                for(int j = i; j <= n; j++) {
                    if(ss[j] == pos)
                        cout << j << " ";
                }
                return 0;
            } 
        }
        return 0;
    }

    Luogu 3387 【模板】缩点

    先进行缩点,后作类似拓扑的操作,$dp$即可。

    #include <bits/stdc++.h>
    const int MAXN = 10050;
    const int MAXM = 100050;
    using namespace std;
    stack<int> ss;
    queue<int> q;
    struct Edge1 {
        int u, to, nextt;
    }edge1[MAXM];
    struct Edge2 {
        int u, to, nextt;
    }edge2[MAXN];
    int n, m, u, v, ans, num1, num2, cnt, cnt1, head1[MAXN], head2[MAXN], dfn[MAXN], low[MAXN], sccno[MAXN], sd[MAXN], in[MAXN], val[MAXN], dis[MAXN];
    void addedge1(int u, int v) {
        edge1[++num1].u = u;
        edge1[num1].to = v;
        edge1[num1].nextt = head1[u];
        head1[u] = num1;
    }
    void addedge2(int u, int v) {
        edge2[++num2].u = u;
        edge2[num2].to = v;
        edge2[num2].nextt = head2[u];
        head2[u] = num2;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        ss.push(x);
        for(int i = head1[x]; i; i = edge1[i].nextt) {
            int to = edge1[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(dfn[x] == low[x]) {
            ++cnt1;
            while(true) {
                int now = ss.top();
                ss.pop();
                sccno[now] = cnt1;
                sd[now] = x;
                if(now == x)
                    break;
                val[x] += val[now];            
            }
        }
    }
    void topo() {
        for(int i = 1; i <= n; i++) {
            if(!in[i] && sd[i] == i) {
                q.push(i);
                dis[i] = val[i];
            }
        }
        while(!q.empty()) {
            int now = q.front();
            q.pop();
            for(int i = head2[now]; i; i = edge2[i].nextt) {
                int to = edge2[i].to;
                dis[to] = max(dis[to], dis[now] + val[to]);
                if(!--in[to])
                    q.push(to);
            }
        }
        for(int i = 1; i <= n; i++)
            ans = max(ans, dis[i]);
    }
    int main() {
        cin >> n >> m;
        for(int i = 1; i <= n; i++)
            cin >> val[i];
        for(int i = 1; i <= m; i++) {
            cin >> u >> v;
            addedge1(u, v);
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i);
        }
        for(int i = 1; i <= m; i++) {
            int u = sd[edge1[i].u], v = sd[edge1[i].to];
            if(u == v)
                continue;
            addedge2(u, v);
            in[v] ++;
        }
        topo();
        cout << ans << endl;
        return 0;
    }

    Luogu 3627 抢掠计划

    先缩点,后跑一遍$spfa$(最长路)即可。

    #include <bits/stdc++.h>
    const int MAXN = 500050;
    const int INF = 1 << 30;
    using namespace std;
    struct Edge1 {
        int u, to, nextt;
    }edge1[MAXN];
    struct Edge2 {
        int u, to, val, nextt;
    }edge2[MAXN];
    stack<int> d;
    queue<int> q;
    int n, m, u, v, w, x, num1, num2, cnt, cnt1, s, p, ans, sd[MAXN], head1[MAXN], head2[MAXN], low[MAXN], dfn[MAXN], vall[MAXN], dis[MAXN], sccno[MAXN];
    bool vis[MAXN];
    int read() {
        int x = 0;
        bool sign = false;
        char alpha = 0;
        while(!isdigit(alpha)) {
            sign |= alpha == '-';
            alpha = getchar();
        }
        while(isdigit(alpha)) {
            x = (x << 1) + (x << 3) + (alpha ^ 48);
            alpha = getchar();
        }
        return sign ? -x : x;
    }
    void addedge1(int u, int v) {
        edge1[++num1].u = u;
        edge1[num1].to = v;
        edge1[num1].nextt = head1[u];
        head1[u] = num1;
    }
    void addedge2(int u, int v, int w) {
        edge2[++num2].u = u;
        edge2[num2].to = v;
        edge2[num2].val = w;
        edge2[num2].nextt = head2[u];
        head2[u] = num2;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        d.push(x);
        for(int i = head1[x]; i; i = edge1[i].nextt) {
            int to = edge1[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(dfn[x] == low[x]) { 
            ++cnt1;
            while(true) {
                int now = d.top();
                d.pop();
                sccno[now] = cnt1;
                sd[now] = x;
                if(x == now)
                    break;
                vall[x] += vall[now];
            }
        }
    }
    void spfa() {
        dis[sd[s]] = vall[sd[s]];
        q.push(sd[s]);
        while(!q.empty()) {
            int now = q.front();
            q.pop();
            vis[now] = false;
            for(int i = head2[now]; i; i = edge2[i].nextt) {
                int to = edge2[i].to, val = edge2[i].val;
                if(dis[to] < dis[now] + val) {
                    dis[to] = dis[now] + val;
                    if(!vis[to]) {
                        vis[to] = true;
                        q.push(to);
                    }
                }
            }
        }    
    }
    int main() {
        n = read();
        m = read();
        for(int i = 1; i <= m; i++) {
            u = read();
            v = read();
            addedge1(u, v);
        }
        for(int i = 1; i <= n; i++)
            vall[i] = read();
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i);
        }
        for(int i = 1; i <= m; i++) {
            u = sd[edge1[i].u];
            v = sd[edge1[i].to];
            w = vall[v];
            if(u == v)
                continue;
            addedge2(u, v, w);
        }
        s = read();
        p = read();
        spfa();
        for(int i = 1; i <= p; i++) {
            x = read();
            ans = max(ans, dis[sd[x]]);
        }
        cout << ans << endl;
        return 0;
    }

    Luogu 2002 消息扩散

    答案显然是缩点后入度为$0$的点的个数。

    #include <bits/stdc++.h>
    const int MAXN = 100050;
    const int MAXM = 500500;
    using namespace std;
    stack<int> ss;
    int n, m, u, v, pos, num, num1, num2, cnt, cnt1, in[MAXN], head1[MAXN], head2[MAXN], low[MAXN], dfn[MAXN], sd[MAXN], sccno[MAXN];
    bool vis[5050][5050];
    struct Edge1 {
        int u, to, nextt;
    }edge1[MAXM];
    struct Edge2 {
        int u, to, nextt;
    }edge2[MAXM];
    int read() {
        int x = 0;
        bool sign = false;
        char alpha = 0;
        while(!isdigit(alpha)) {
            sign |= alpha == '-';
            alpha = getchar();
        }
        while(isdigit(alpha)) {
            x = (x << 1) + (x << 3) + (alpha ^ 48);
            alpha = getchar();
        }
        return sign ? -x : x;
    }
    void addedge1(int u, int v) {
        edge1[++num1].u = u;
        edge1[num1].to = v;
        edge1[num1].nextt = head1[u];
        head1[u] = num1;
    }
    void addedge2(int u, int v) {
        edge2[++num2].u = u;
        edge2[num2].to = v;
        edge2[num2].nextt = head2[u];
        head2[u] = num2;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        ss.push(x);
        for(int i = head1[x]; i; i = edge1[i].nextt) {
            int to = edge1[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(dfn[x] == low[x]) {
            ++cnt1;
            while(true) {
                int now = ss.top();
                ss.pop();
                sccno[now] = cnt1;
                sd[now] = cnt1;
                if(now == x)
                    break;        
            }
        }
    }
    int main() {
        n = read(); 
        m = read();
        for(int i = 1; i <= m; i++) {
            u = read();
            v = read();
            if(u == v)
                continue;
            addedge1(u, v);
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i);
        }
        for(int i = 1; i <= num1; i++) {
            u = sd[edge1[i].u];
            v = sd[edge1[i].to];
            if(u == v)
                continue;
            in[v]++;
            addedge2(u, v);
        }
        for(int i = 1; i <= cnt1; i++) {
            if(!in[i])
                num++;
        }
        cout << num << endl;
        return 0;
    }

    Luogu 2746 Network of Schools

    第一问的答案显然是缩点后入度为$0$的点的个数。

    对于第二问,若存在出度为$0$的点或者入度为$0$的点则不满足条件。若把一个出度为$0$的点与一个入度为$0$的点连一条边,问题便迎刃而解。

    考虑到两种点的个数不一定相同,选取其中的最大值即可。

    记得判断是一个大环的情况。

    #include <bits/stdc++.h>
    const int MAXN = 250000;
    using namespace std;
    struct Edge1 {
        int u, to, nextt;
    }edge1[MAXN << 1];
    struct Edge2 {
        int u, to, nextt;
    }edge2[MAXN << 1];
    stack<int> s;
    int n, u ,v, cnt, cnt1, ans1, num, num1, num2, head1[MAXN], head2[MAXN], dfn[MAXN], low[MAXN], sccno[MAXN], sd[MAXN], in[MAXN], out[MAXN];
    void addedge1(int u, int v) {
        edge1[++num1].u = u;
        edge1[num1].to = v;
        edge1[num1].nextt = head1[u];
        head1[u] = num1;
    }
    void addedge2(int u, int v) {
        edge2[++num2].u = u;
        edge2[num2].to = v;
        edge2[num2].nextt = head2[u];
        head2[u] = num2;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        s.push(x);
        for(int i = head1[x]; i; i = edge1[i].nextt) {
            int to = edge1[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(low[x] == dfn[x]) {
            ++cnt1;
            while(true) {
                int now = s.top();
                s.pop();
                sd[now] = cnt1;
                sccno[now] = cnt1;
                if(now == x)
                    break;
            }
        }
    }
    int main() {
        cin >> n;
        for(int i = 1; i <= n; i++) {
            while(scanf("%d", &v) == 1) {
                if(v == 0)
                    break;
                addedge1(i, v);
            }
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i);
        }
        for(int i = 1; i <= num1; i++) {
            u = sd[edge1[i].u];
            v = sd[edge1[i].to];
            if(u == v)
                continue;
            addedge2(u, v);
            in[v]++;
            out[u]++;
        }
        for(int i = 1; i <= cnt1; i++) {
            if(in[i] == 0)
                ans1++;
            if(out[i] == 0)
                num++;
        }
        cout << ans1 << endl;
        if(cnt1 == 1) {
            cout << "0" << endl;
            return 0;
        }
        cout << max(num, ans1) << endl;
        return 0;
    } 

    Luogu 2341 受欢迎的牛

    若出现个数 ge 1的出度为$0$的强连通分量,则无解。

    $otherwise$答案即为出度为$0$的强连通分量的大小。

    #include <bits/stdc++.h>
    const int MAXN = 10050;
    const int MAXM = 50050;
    using namespace std;
    stack<int> ss;
    int n, m, u, v, pos, num, num1, num2, cnt, cnt1, head1[MAXN], head2[MAXN], low[MAXN], dfn[MAXN], sd[MAXN], f[MAXN], sccno[MAXN];
    struct Edge1 {
        int u, to, nextt;
    }edge1[MAXM];
    struct Edge2 {
        int u, to, nextt;
    }edge2[MAXM];
    int read() {
        int x = 0;
        bool sign = false;
        char alpha = 0;
        while(!isdigit(alpha)) {
            sign |= alpha == '-';
            alpha = getchar();
        }
        while(isdigit(alpha)) {
            x = (x << 1) + (x << 3) + (alpha ^ 48);
            alpha = getchar();
        }
        return sign ? -x : x;
    }
    void addedge1(int u, int v) {
        edge1[++num1].u = u;
        edge1[num1].to = v;
        edge1[num1].nextt = head1[u];
        head1[u] = num1;
    }
    void addedge2(int u, int v) {
        edge2[++num2].u = u;
        edge2[num2].to = v;
        edge2[num2].nextt = head2[u];
        head2[u] = num2;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        ss.push(x);
        for(int i = head1[x]; i; i = edge1[i].nextt) {
            int to = edge1[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(dfn[x] == low[x]) {
            ++cnt1;
            while(true) {
                int now = ss.top();
                ss.pop();
                sccno[now] = cnt1;
                sd[now] = cnt1;
                f[cnt1] ++;
                if(now == x)
                    break;        
            }
        }
    }
    int main() {
        n = read(); 
        m = read();
        for(int i = 1; i <= m; i++) {
            u = read();
            v = read();
            addedge1(u, v);
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i])
                tarjan(i);
        }
        for(int i = 1; i <= m; i++) {
            u = sd[edge1[i].u];
            v = sd[edge1[i].to];
            if(u == v)
                continue;
            addedge2(u, v);
        }
        for(int i = 1; i <= cnt1; i++) {
            if(!head2[i]) {
                pos = i;
                num++;
            }
            if(num == 2) {
                cout << "0" << endl;
                return 0;
            }
        }
        cout << f[pos] << endl;
        return 0;
    }

    Luogu 1262 间谍网络

    考虑无解的情况。若存在一个点既不能被贿赂,也没有入度,则不存在答案。

    先缩点。对于现在没有入度且原先在强连通分量中的点,代价即为这些点中花费最小的值,累加即可。

    #include <bits/stdc++.h>
    const int MAXN = 100050;
    const int INF = 1 << 30;
    int n, m, u, v, p, num1, num2, cnt, cnt1, ans, in[MAXN], dfn[MAXN], low[MAXN], head1[MAXN], head2[MAXN], sccno[MAXN], val[MAXN], vall[MAXN], sd[MAXN];
    using namespace std;
    stack<int> s;
    struct Edge1 {
        int u, to, nextt;
    }edge1[MAXN];
    int read() {
        int x = 0;
        bool sign = false;
        char alpha = 0;
        while(!isdigit(alpha)) {
            sign |= alpha == '-';
            alpha = getchar();
        }
        while(isdigit(alpha)) {
            x = (x << 1) + (x << 3) + (alpha ^ 48);
            alpha = getchar();
        }
        return sign ? -x : x;
    }
    void addedge1(int u, int v) {
        edge1[++num1].u = u;
        edge1[num1].to = v;
        edge1[num1].nextt = head1[u];
        head1[u] = num1;
    }
    void tarjan(int x) {
        dfn[x] = low[x] = ++cnt;
        s.push(x);
        for(int i = head1[x]; i; i = edge1[i].nextt) {
            int to = edge1[i].to;
            if(!dfn[to]) {
                tarjan(to);
                low[x] = min(low[x], low[to]);
            }
            else if(!sccno[to])
                low[x] = min(low[x], dfn[to]);
        }
        if(dfn[x] == low[x]) {
            ++cnt1;
            while(true) {
                int now = s.top();
                s.pop();
                sd[now] = cnt1;
                sccno[now] = cnt1;
                vall[cnt1] = min(vall[cnt1], val[now]);
                if(now == x)
                    break;
            }
        }
    }
    int main() {
        n = read();
        p = read();
        for(int i = 1; i <= n; i++)
            val[i] = vall[i] = INF;
        for(int i = 1; i <= p; i++) {
            u = read();
            val[u] = read();
        }
        m = read();
        for(int i = 1; i <= m; i++) {
            u = read();
            v = read();
            in[v]++;
            addedge1(u, v);
        }
        for(int i = 1; i <= n; i++) {
            if(val[i] == INF && in[i] == 0) {
                cout << "NO" << endl << i << endl;
                return 0;
            }
        }
        for(int i = 1; i <= n; i++) {
            if(!dfn[i] && val[i] != INF)
                tarjan(i);
        }
        for(int i = 1; i <= n; i++)
            in[i] = 0;
        for(int i = 1; i <= num1; i++) {
            u = sd[edge1[i].u];
            v = sd[edge1[i].to];
            if(u == v)
                continue;
            in[v]++;
        }
        for(int i = 1; i <= cnt1; i++) {
            if(in[i] == 0)
                ans += vall[i];
        }
        cout << "YES" << endl << ans << endl;
        return 0;
    }
  • 相关阅读:
    Leetcode刷题笔记
    Leetcode刷题笔记
    朋友发来的图片,要制作成身份证复印件,怎么办?
    记录一次MAC连接投影闪屏的问题。
    win10系统 端口查看问题。
    使用Windows命令行reg控制注册表键值
    SVN钩子HOOK设置自动备份,服务本地可以看到所有更新内容。
    报错代码:svn-http status413'requset entity too large
    SVN: Cleanup failed update报错 文件被锁定lock办法,cleanup 失效报错。
    Samba centos7文件共享服务器搭建教程,可以更改任意需求操作配置详解。
  • 原文地址:https://www.cnblogs.com/BeyondLimits/p/11253239.html
Copyright © 2011-2022 走看看