tarjanアルゴリズム演習

DFSツリー・アルゴリズムとtarjan

タグ(スペースで区切っ):517coding問題解決のDFSツリーtarjan


タスク1

無向グラフの所与\(G \) 前記所与のツリーDFSにおける\(T \)を選択し、\(G \)エッジの集合\(E \)をすべてのT-単純ように、 -Circle(すなわち、側までない(T \)\パス)を少なくとも一つ含む\(E \)最小限の要素、\(E \)の要素の数。
データの100%に\(1 \当量のn \の当量
2 \回10 ^ 3、1 \当量のm個の\当量2 \回10 ^ 4 \) でなければならないエッジを選択するために考慮に入れて\(T \)ようにするために電流経路ツリーノードとその祖先ノードに接続されたリングからなる単純なパスは、このパス上のエッジを選択するために選択されなければなりません。

対応する子ノードに下縁、

考慮したノードの貪欲、単にチェーンを降順にソート浅く、各チェーン現在のポイントがカバーされていない場合、それは浅い点カバレッジとなり、このセグメントが次のセグメントのカバーされたそのものであります浅い深度の効果の点より低い点以上です。

チェーン分割ツリーは達することができるが(O(N {log_2} ^ 2 N)\)\ 複雑さが、必要ありません。

最終的な複雑さがある\(O(N ^ 2) \)

# include<bits/stdc++.h>
using namespace std;
const int N=2e3+10,M=2e4+10;
struct rec{ int pre,to; }a[M<<1];
struct node{ int u,v; }rec[N];
int n,m,tot=1,g;
int head[N],dep[N],pre[N];
bool mark[N],inTree[M<<1];
vector<node>V;
void clear()
{
    tot=1;
    memset(a,0,sizeof(a)); memset(rec,0,sizeof(rec));
    memset(dep,0,sizeof(dep)); memset(head,0,sizeof(head));
    memset(pre,0,sizeof(pre)); memset(mark,false,sizeof(mark));
    memset(inTree,false,sizeof(inTree)); V.clear();
}
void adde(int u,int v,bool flag)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    head[u]=tot;
    if (flag) inTree[tot]=1;
}
void dfs(int u,int fa)
{
    dep[u]=dep[fa]+1;
    for (int i=head[u];i;i=a[i].pre){
        int v=a[i].to; if (v==fa) continue;
        pre[v]=u; dfs(v,u);
    }
}
bool cmp(node x,node y){return dep[x.v]>dep[y.v];}
bool check(int s,int t)
{
    while (pre[s]!=t) {
        if (mark[s]) return true;
        s=pre[s];
    }
    if (mark[s]) return true;
    g=s;
    return false;
}
int main()
{
    //freopen("data.in","r",stdin); 
    while (true) {
        scanf("%d%d",&n,&m); if (!n&&!m) break;
        for (int i=1;i<n;i++) {
            int u,v; scanf("%d%d",&u,&v);
            adde(u,v,1); adde(v,u,1);
            rec[i]=(node){u,v};
        }
        dfs(1,0);
        for (int i=n;i<=m;i++) {
            int u,v; scanf("%d%d",&u,&v);
            adde(u,v,0); adde(v,u,0);
        }
        for (int i=1;i<n;i++) {
            int u=rec[i].u;
            for (int w=head[u];w;w=a[w].pre){
                int v=a[w].to; if (dep[v]>dep[u]||inTree[w]) continue;
                V.push_back((node){u,v});
            }
            u=rec[i].v;
            for (int w=head[u];w;w=a[w].pre){
                int v=a[w].to; if (dep[v]>dep[u]||inTree[w]) continue;
                V.push_back((node){u,v});
            }
        }
        sort(V.begin(),V.end(),cmp);
        int ans=0;
        for (int i=0;i<V.size();i++) {
            int s=V[i].u,t=V[i].v;
            if (check(s,t)) continue;
            else mark[g]=1,ans++;
        }
        printf("%d\n",ans);
        clear();
    }
    return 0;
}

タスク2

無向グラフで\(G \)でいずれかのスパニングツリーを取る、スパニングツリーは、側面を介して結合されているが得られます。
データの100%$ 2 \当量N \の当量〜10 ^ 4、1 \当量のM \の当量10 ^ 5 $

エッジは、エッジを切断された場合は、スパニングツリーに表示されるように結合されています。
すべての切刃を得るには、この問題。

ノードは、DFSツリーがエッジ配列に接続することができない後のDFS DFNツリーDFS内のノードへのプログラム番号を表していない、請求tarjanアルゴリズムを使用し、低最小のDFSノードを意味します。

明らかに、エッジ決意条件用刃先\は((u、v)は最先端の\である)、場合に限り$ low_ {V}> dfn_ { U} $

重い方ならば、あなたはまだ扱うことができ、明らかに唯一のノード番号の側に電流について書きたい(変換ペアリングすることができます)親ノードに戻って、このエッジに電流がそのエッジでない場合、それは2を示しています重いエッジを含むダイレクトノード、エッジが一度量を有するが、その後、父の子ノードの低い値を更新順序DFSができます。

この質問は、それを終了する(\ O(N))\

# include<bits/stdc++.h>
using namespace std;
const int N=1e4+10,M=1e5+10;
struct rec{ int pre,to; }a[M<<1];
int dfn[N],low[N],n,m,head[N],tot=1;
bool bridge[M<<1];
void clear()
{
    tot=1;
    memset(a,0,sizeof(a)); memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low)); memset(head,0,sizeof(head));
    memset(bridge,false,sizeof(bridge));
}
void adde(int u,int v)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    head[u]=tot;
}
void tarjan(int u,int id)
{
    low[u]=dfn[u]=++dfn[0];
    for (int i=head[u];i;i=a[i].pre) {
        int v=a[i].to; 
        if (!dfn[v]) { 
            tarjan(v,i);  low[u]=min(low[u],low[v]);
            if (low[v]>dfn[u]) bridge[i]=bridge[i^1]=1;
        } else if (i!=(id^1)) low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
    int T; scanf("%d",&T);
    while (T--) {
        scanf("%d%d",&n,&m);
        for (int i=1;i<=m;i++) {
            int u,v; scanf("%d%d",&u,&v);
            adde(u,v); adde(v,u);
        }
        tarjan(1,0); int cnt=0;
        for (int i=2;i<=tot;i+=2) if (bridge[i]) cnt++;
        printf("%d\n",cnt);
        for (int i=2;i<=tot;i+=2) 
            if (bridge[i]) printf("%d%c",i>>1,(--cnt)==0?'\n':' ');
        if (T!=0) puts("");
        clear();
    }
    return 0;
 } 

タスク3

カットポイントを見つけるませんマップ

それはとても複雑な入力は、理由は私にはわからない(水、それのこの質問の本質をカバーするには?)

# include <iostream>
# include <cstdio>
# include <cstring>
# define min(a,b) ((a)<(b)?(a):(b))
# define max(a,b) ((a)<(b)?(b):(a))
using namespace std;
const int N=105,M=N*N;
struct rec{ int pre,to; }a[M<<1];
bool cur[N];
int dfn[N],low[N],head[N],root;
int n,m,tot;
void clear()
{
    memset(a,0,sizeof(a)); memset(cur,false,sizeof(cur));
    memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low));
    memset(head,0,sizeof(head)); tot=0;
}
void adde(int u,int v)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    head[u]=tot;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++dfn[0];
    int flag=0;
    for (int i=head[u];i;i=a[i].pre) {
        int v=a[i].to;
        if (!dfn[v]){ 
            tarjan(v); low[u]=min(low[u],low[v]);
            if (low[v]>=dfn[u]) {
                flag++; 
                if (u!=root || flag>1) cur[u]=1;
            }
        } else low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
    while (true) {
        scanf("%d",&n); if (!n) break;
        while (true) {
            int u; scanf("%d",&u); if (!u) break;
            while (getchar()!='\n') {
                int v; scanf("%d",&v);
                adde(u,v); adde(v,u);
            }
        }
        for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(root=i);
        int ans=0;
        for (int i=1;i<=n;i++) if (cur[i]) ans++;
        printf("%d\n",ans);
        clear();
    }
    return 0;
}

タスク4

有向グラフ(G \)\から各ノードの重さ、た\(S \)するための出発点(P \)\正当なエンドポイントは、有効なパスであると見なされます。パスを選択し、あなたには、いくつかのポイントの後に繰り返すことができる(ただし、その重量は一度しか得ることができ)、および重量を通るパスを最大化すること。
100%のデータ$ 1 \当量Pの\の当量のN 、Mの\の当量5 \回10 ^ 5 $

リングがある場合を考慮すると、最良の方法は、所有権の値を取るリングの周りに一度行くことです。

だから我々は、使用\(tarjanの\)に、ポイントを縮小するために、問題をアルゴリズムを\(\ DAG)の` \(S \)の任意の点までの最長の道路の出発点。(リングノードの答えは同じです)

あなたはDP実際、使用することができます\(DAG \)トポロジカル整列、トポロジカル順序に従って行わ動的なプログラミングを。

減少ためのトポロジー点があれば、\(S \)トポロジは、その後、シーケンスの前に\(S \)そこに行くことは不可能です。

セット\(f_uが\)を表し\(S \)をポイント\(U \)最長のウェイポイントを、転送がされている(f_u =最大\ {F_ \ + \和w_uの\}からの{} \)

その答えは、正当な目的地の最大収縮ポイントを含めることです。

また、出発の最初のポイントに加えて、このようなDPなどいくつかの問題は、他のすべての-inf設定しなければならないことに注意してください
によるDP最大に時間を取ることですので、重い側は問題になりません。

# include <iostream>
# include <cstdio>
# include <cstring>
# include <vector>
# include <stack>
# include <queue>
# include <algorithm>
# define int long long
using namespace std;
const int N=5e5+10;
struct rec{ int pre,to; }a[N<<1];
stack<int>s; queue<int>q;
bool ok[N],ins[N];
int dfn[N],low[N],c[N],val[N],w[N],head[N],seq[N],in[N],f[N];
vector<int>E[N],F[N];
vector<int>scc[N];
vector<pair<int,int> >Edge;
int n,m,tot,cnt;
void adde(int u,int v)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    head[u]=tot;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++dfn[0];s.push(u);ins[u]=1;
    for (int i=head[u];i;i=a[i].pre){
        int v=a[i].to;
        if (!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
        else if (ins[v]) low[u]=min(low[u],dfn[v]);
    }
    if (dfn[u]==low[u]) {
        int v; ++cnt;
        do {
            v=s.top(); s.pop(); ins[v]=0;
            c[v]=cnt; scc[cnt].push_back(v);
            val[cnt]+=w[v];
        } while (v!=u);
    }
}
void toposort(int s)
{
    if (!in[s]) q.push(s);
    for (int i=1;i<=cnt;i++) if (!in[i] && i!=s) q.push(i);
    while (!q.empty()) {
        int u=q.front();q.pop();
        seq[++seq[0]]=u;
        for (int i=0;i<E[u].size();i++) {
            int v=E[u][i]; 
            in[v]--; if (!in[v]) q.push(v);
        }
    }
}
signed main()
{
    scanf("%lld%lld",&n,&m);
    for (int i=1;i<=m;i++) {
        int u,v; scanf("%lld%lld",&u,&v);
        adde(u,v);
    }
    for (int i=1;i<=n;i++) scanf("%lld",&w[i]);
    int s,p; scanf("%lld%lld",&s,&p);
    for (int i=1;i<=p;i++) { int t; scanf("%lld",&t); ok[t]=1;}
    for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
    for (int u=1;u<=n;u++)
     for (int i=head[u];i;i=a[i].pre) {
        int v=a[i].to; if (c[u]==c[v]) continue;
        Edge.push_back(make_pair(c[u],c[v]));
     }
    sort(Edge.begin(),Edge.end()); 
    Edge.erase((unique(Edge.begin(),Edge.end())),Edge.end()); 
    for (int i=0;i<Edge.size();i++) {
        int u=Edge[i].first,v=Edge[i].second;
        E[u].push_back(v); in[v]++;
        F[v].push_back(u);
    } 
    toposort(s);
    int pt; for (int i=1;i<=cnt;i++) if (c[s]==seq[i]) { pt=i; break;} 
    memset(f,-0x3f,sizeof(f)); f[c[s]]=val[c[s]]; 
    for (int w=pt+1;w<=cnt;w++) {
        int u=seq[w];
        for (int i=0;i<F[u].size();i++) {
            int v=F[u][i];
            f[u]=max(f[u],f[v]);
        } 
        f[u]+=val[u];
    }
    int ans=0;
    for (int i=1;i<=n;i++) if (ok[i]) ans=max(ans,f[c[i]]);
    printf("%lld\n",ans); 
    return 0;
}

タスク5

有向グラフ\(G \)少なくとも各ノードは、ノードからエッジを向けることができるように追加されるがいくつあるかに\(S \)出発アクセスします。
データの100%に\(1 \当量S \当量のN、Mの\の当量5000 \)

ポイントを縮小した後、図面はになって\(DAG \)の問題。

DFS $再びそれを行う\に(とすべて\見つけることができません) S \(リンクブロック、各ブロックの側面の順と\接続が少なくとも必要となるポイントに)ポイントのS $の接続を。

それは必然的に接続されるように、ブロック0プラスそのエッジの各々との通信の程度を考慮し\(S \)は、通信ブロックが配置されている、または他の通信ブロック。

そう答えが離れている(S \)\通信におけるポイントを凝縮し、ブロックの数がゼロです。

複雑\(O(n)は、\)

# include <bits/stdc++.h>
using namespace std;
const int N=5005;
struct rec{ int pre,to; }a[N<<1];
stack<int>s;
int low[N],dfn[N],head[N],c[N],in[N];
bool ins[N],vis[N];
int n,m,tot,cnt;
vector<int>E[N];
void adde(int u,int v)
{
    a[++tot].pre=head[u];
    a[tot].to=v;
    head[u]=tot;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++dfn[0]; s.push(u); ins[u]=1;
    for (int i=head[u];i;i=a[i].pre){
        int v=a[i].to; 
        if (!dfn[v]) tarjan(v),low[u]=min(low[u],low[v]);
        else if (ins[v]) low[u]=min(low[u],dfn[v]);
    }
    if (low[u]==dfn[u]) {
        int v; ++cnt;
        do {
            v=s.top();s.pop();ins[v]=0;
            c[v]=cnt; 
        } while (v!=u);
    }
}
void dfs(int u)
{
    vis[u]=1;
    for (int i=0;i<E[u].size();i++) {
        int v=E[u][i]; if (vis[v]) continue;
        dfs(v);
    }
}
int main()
{
    int s; scanf("%d%d%d",&n,&m,&s);
    for (int i=1;i<=m;i++) {
        int u,v; scanf("%d%d",&u,&v);
        adde(u,v);
    }
    for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
    for (int u=1;u<=n;u++)
     for (int i=head[u];i;i=a[i].pre) {
        int v=a[i].to; if (c[u]==c[v]) continue;
        E[c[u]].push_back(c[v]); in[c[v]]++;
    }
    dfs(c[s]); int ans=0;
    for (int i=1;i<=cnt;i++)  if (!vis[i] && !in[i] ) ans++;
    printf("%d\n",ans);
    return 0;
}

おすすめ

転載: www.cnblogs.com/ljc20020730/p/11291475.html