tarjan 算法

pku 2762 Going from u to v or from v to u?

题意:给定一个有向图,对任意一对x,y,判断是否存在x到y 或 y到x 的路径。。。

分析:首先,可以想到的是,强连通缩点,因为对于一个极大强连通分量,任意俩点均可达,所以先缩成一个点,,缩点之后,得到的是一个DAG,判断图是否为弱连通图==》即判断拓扑序列是否唯一

pku2762
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

const int N = 1000+10;

vector<int> g[N],g1[N];
stack<int> st;

int dfn[N],low[N],f[N],n,num,index;
bool instack[N],vis[N];
int deg[N];

void tarjan(int u)
{
    int v;
    vis[u]=true;
    dfn[u]=low[u]=index++;
    st.push(u);
    instack[u]=true;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        do
        {
            v=st.top();
            st.pop();
            instack[v]=false;
            f[v]=num;
        }while(v!=u);
        num++;
    }
}
bool topo()
{
    queue<int> Q;
    for(int i=0;i<num;++i)
        if(deg[i]==0)
            Q.push(i);
    if(Q.size()>=2) return false; 
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        for(int i=0;i<g1[u].size();i++)
        {
            --deg[g1[u][i]];
            if(deg[g1[u][i]]==0)
                Q.push(g1[u][i]);
        }
        if(Q.size()>=2)
            return false;
    }
    return true;
}
int main()
{
    int T,m;
    int a,b;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&n,&m);
        for(int i=0;i<=n;++i)
        {
            g[i].clear();
            g1[i].clear();
        }
        while(m--)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(b);
        }
        memset(instack,false,sizeof(instack));
        memset(vis,false,sizeof(vis));
        index=num=0;
        for(int i=1;i<=n;i++)
            if(!vis[i])
                tarjan(i);
        memset(deg,0,sizeof(deg));
        for(int i=1;i<=n;i++)
            for(int j=0;j<g[i].size();j++)
            {
                if(f[i]!=f[g[i][j]])
                {
                    g1[f[i]].push_back(f[g[i][j]]);
                    ++deg[f[g[i][j]]];
                }
            }
        if(topo())
            puts("Yes");
        else puts("No");
    }
    return 0;
}

 

pku 3592 Instantaneous Transference

题意:在一个给定的N *M 的矩阵上,数字代表该位置的矿物数,,* 代表该位置可以选择是否移动到某一个特定的位置,#不可通过,从左上角出发,每次只能向右或向下,或者某些位置的瞬间移动,问可能得到的矿物数的最大值(每个位置的矿物只能取一次)

分析:若不存在瞬间移动的可能,则可以直接建图,得到的是一个DAG ,然后直接一遍dfs 即可,,问题是有了瞬间移动的可能,所以需要对该图求强连通分量,缩点,同样得到一个DAG ,,

pku3592
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

const int N = 1600+10;

int dfn[N],low[N],f[N],num,index;
bool instack[N],vis[N];

vector<int> g[N],g1[N];
stack<int> st;

int x[N],y[N],w[N],ww[N];
char map[50][50];
void init(int n)
{
    for(int i=0;i<n;i++)
    {
        g[i].clear();
        g1[i].clear();
    }
    memset(vis,false,sizeof(vis));
    memset(instack,false,sizeof(instack));
    memset(ww,0,sizeof(ww));
    memset(w,0,sizeof(w));
    num=index=0;
}
void tarjan(int u)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    instack[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        do
        {
            v=st.top();
            st.pop();
            instack[v]=false;
            f[v]=num;
            //cout<<num<<' '<<v<<' '<<w[v]<<endl;
            ww[num]+=w[v];
        }while(v!=u);
        num++;
    }
}

int dfs(int u)
{
    int temp=0;
    for(int i=0;i<g1[u].size();++i)
        temp=max(temp,dfs(g1[u][i]));
    //cout<<temp+ww[u]<<endl;
    return temp+ww[u];
}

int main()
{
    int n,m,a,b;
    int T;
    scanf("%d",&T);
    while(T--)
    {

        scanf("%d %d",&n,&m);
        init(n*m);
        int k=0;
        for(int i=0;i<n;i++)
            scanf("%s",map[i]);
        for(int i=0;i<n;++i)
            for(int j=0;j<m;++j)
            {
                if(map[i][j]=='*')
                    x[k]=i,y[k++]=j;
                if(map[i][j]=='#')
                    continue;
                if(map[i][j]!='*')
                    w[i*m+j]=map[i][j]-'0';
                if(j+1<m && map[i][j+1]!='#')
                    g[i*m+j].push_back(i*m+j+1);
                if(i+1<n && map[i+1][j]!='#')
                    g[i*m+j].push_back((i+1)*m+j);
            }
        for(int i=0;i<k;++i)
        {
            scanf("%d %d",&a,&b);
            g[x[i]*m+y[i]].push_back(a*m+b);
        }
        for(int i=0;i<n*m;++i)
            if(!vis[i])
                tarjan(i);
        for(int i=0;i<n*m;++i)
            for(int j=0;j<g[i].size();++j)
            {
                if(f[i]!=f[g[i][j]])
                    g1[f[i]].push_back(f[g[i][j]]);
            }
        //for(int i=0;i<num;i++)
        //    cout<<ww[i]<<endl;
        int ans=dfs(f[0]);
        printf("%d\n",ans);
    }
    return 0;
}

 

pku 2117 Electricity

题意:给定一个无向图,,无重边,问删除一个点之后,可以得到的最大的连通分量数

分析:首先,都知道,删除割点,可以得到多个连通分量,可以得到的连通分量数==与该割点连接的点双连通分支数

        若该图本身是连通的,则ans==最大的与某一个割点连接的点双连通分支数;

        若本身不连通,还需要加上一开始的连通分支数-1

pku2117
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdio.h>
#include<stdlib.h>
#include<vector>
#include<stack>
#include<set>
using namespace std;

const int N = 10000+10;

int dfn[N],low[N],n,index;
int cut[N];
bool vis[N];
vector<int> g[N];

void init()
{
    for(int i=0;i<n;i++)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(cut,0,sizeof(cut));
    index=0;
}
void tarjan(int u,int p)
{
    int v;
    dfn[u]=low[u]=index++;
    vis[u]=true;
    int sun=0;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v,u);
            ++sun;
            low[u]=min(low[u],low[v]);
            if((p==-1 && sun>1)||(p!=-1 && dfn[u]<=low[v]))
            {
                ++cut[u];
            }
        }
        else if(v!=p)
            low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
    int m;
    int a,b;
    while(scanf("%d %d",&n,&m)==2 && (n||m))
    {

        if(n==1) {
            printf("0\n");
            continue;
        }
        if(m==0) {
            printf("%d\n",n-1);
            continue;
        }    
        init();    
        while(m--)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        int t=0;
        for(int i=0;i<n;i++)
            if(!vis[i])
            {
                ++t;
                tarjan(i,-1);
            }
        int ans=0;
        for(int i=0;i<n;i++)
        {
            if(cut[i])
                ans=max(ans,cut[i]);
        }
        printf("%d\n",ans+t);
    }
    return 0;
}

 

pku2942 Knights of the Round Table

题意:N个骑士开圆桌会议, 有些骑士不能相邻, 要求每桌奇数个人, 每桌至少3个人, 问哪些骑士
永远不能参加圆桌会议.

永远不能参加会议的人数!!而不是最少不能参加的人数!!所以,图可以不连通

分析:首先对于每一个点双连通分量,判断该连通分量是否存在连接所有点的奇圈,若存在,则很明显,该点双连通分量中所有点都是可能参加圆桌会议的,,又因为,对于一个点双连通分量,若存在一个奇圈,则该连通分量在所有点都在一个更大的奇圈中,所以,对于每一个点连通分量,只需要判断是否存在奇圈即可。。

注意:割点同时属于多个点双连通分量

pku2942
//求双连通分量 , 判奇圈(用染色法)
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<string.h>
//题意:N个骑士开圆桌会议, 有些骑士不能相邻, 要求每桌奇数个人, 每桌至少3个人, 问哪些骑士
//永远不能参加圆桌会议.

//永远不能参加会议的人数!!而不是最少不能参加的人数!!所以,图可以不连通
#include<stdio.h>
#include<stdlib.h>
using namespace std;

const int N = 1000+10;
int n,low[N],dfn[N],index,set[N],col[N];
bool map[N][N],vis[N],scc[N],flag[N];
//scc[] 标记一个点是否在当前的双连通分量中
//set[] 当前双连通分量的点集
//flag[] 标记一个点是否在奇圈中

vector<int> g[N];
stack<int> st;
bool dfs(int u,int c)//染色法,判断是否存在奇圈
{
    col[u]=c;
    for(int i=0;i<g[u].size();++i)
    {
        int v=g[u][i];
        if(!scc[v]) continue;
        if(col[v]==-1)
        {
            if(dfs(v,1-c))
                return true;
        }
        else if(col[v]==col[u])
            return true;
    }
    return false;
}
void check(int k)
{
    int i;
    memset(col,-1,sizeof(col));
    if(dfs(set[0],0))
    {
        for(int i=0;i<k;i++)
            flag[set[i]]=true;
    }
    memset(scc,false,sizeof(scc));
}
void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    for(int i=0;i<g[u].size();++i)
    {
        int v=g[u][i];
        if(v==p) continue;
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<=low[v])
            {
                int tmp,k=1;
                set[0]=u;
                do
                {
                    tmp=st.top();
                    st.pop();
                    scc[tmp]=true;
                    set[k++]=tmp;
                }while(tmp!=v);
                check(k);
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
}
void init()
{
    for(int i=0;i<n;++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(scc,false,sizeof(scc));
    memset(flag,false,sizeof(flag));
    index=0;
    for(int i=0;i<n;++i)
        for(int j=i+1;j<n;++j)
            if(!map[i][j])
            {
                g[i].push_back(j);
                g[j].push_back(i);
            }
}
    
int main()
{
    int a,b,m;
    while(scanf("%d %d",&n,&m)==2 && (n||m))
    {
        memset(map,false,sizeof(map));
        while(m--)
        {
            scanf("%d %d",&a,&b);
            --a;--b;
            map[a][b]=map[b][a]=true;
        }
        init();
        for(int i=0;i<n;++i)
            if(!vis[i])
                tarjan(i,-1);
        int ans=0;
        for(int i=0;i<n;++i)
            if(flag[i])
                ++ans;
        printf("%d\n",n-ans);
    }
    return 0;
}

 

pku3160 Father Christmas flymouse

题意 :在一个有向图中,选择一个点出发,沿着有向边走,每一个点都有一个权值(可能为负),但只能获得该点的权值一次,可以选择不要(负数的时候),问最终可能获得的最大权值和

分析:此题与上面的pku3592类似,建图完成之后就一样了,只是这里权值可能为负,要特殊考虑一下

pku3160
#include<iostream>
#include<algorithm>
#include<stack>
#include<vector>
using namespace std;

const int N = 30000+10;

int dfn[N],low[N],f[N],n,num,index;
bool vis[N],instack[N];
int w[N],ww[N],deg[N];
vector<int> g[N],g1[N];
stack<int> st;

void tarjan(int u)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    instack[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        do
        {
            v=st.top();
            st.pop();
            instack[v]=false;
            f[v]=num;
            ww[num]+=w[v]>0?w[v]:0;
        }while(v!=u);
        num++;
    }
}

void init()
{
    for(int i=0;i<n;++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(instack,false,sizeof(instack));
    memset(ww,0,sizeof(ww));
    memset(deg,0,sizeof(deg));
    num=index=0;
}
int dfs(int u)
{
    int ret=ww[u],tmp=0;
    if(ret<0) ret=0;
    for(int i=0;i<g1[u].size();++i)
        tmp=max(tmp,dfs(g1[u][i]));
    return ret+=tmp;
}

int main()
{
    int m;
    int a,b;
    while(scanf("%d %d",&n,&m)==2)
    {
        init();
        for(int i=0;i<n;++i)
            scanf("%d",&w[i]);
        while(m--)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(b);
        }
        for(int i=0;i<n;++i)
            if(!vis[i])
                tarjan(i);
        for(int i=0;i<num;++i)
            g1[i].clear();
        for(int i=0;i<n;++i)
            for(int j=0;j<g[i].size();++j)
                if(f[i]!=f[g[i][j]])
                {
                    g1[f[i]].push_back(f[g[i][j]]);
                    deg[f[g[i][j]]]++;
                }
        int ans=0;
        for(int i=0;i<num;++i)
        {
            if(deg[i]==0)//当然是选择入度为零的点开始搜索
                ans=max(ans,dfs(i));
        }
        printf("%d\n",ans);
    }
    return 0;
}
    

 

pku1904 King's Quest

题意:给定一个2*N个顶点的2分图,并且给了一个完美匹配(Perfect Matching)以及每个顶点可以连接的其他的顶点。
//题目要求是否可以确定某2个顶点连边后,其他2*(N - 1)个顶点的2分图是否可以构成完美匹配。
//分析:
//题目给了你一个初始的最大匹配 怎样用好这个匹配是非常关键的
//首先把图转化成有向图(因为后面我们要用到连通性判定)
//现在令王子为A集合,公主为B集合
//原图中所有的边转化成A->B的边 然后对原来的每一个匹配Ai->Bj 添加一条从Bj到Ai的边
//结论: 如果Ai,Bj能够互访 我们则认为Ai, Bj作为一条匹配边仍然不会影响其他王子和自己心爱的人匹配
//证明如下:
//现在有一个匹配Ai->Bj 我们知道Ai和Bj可以互访
//我们要验证其是否可以成立时对其他王子找MM没有影响
//1。如果题目中给的初始匹配包含这条边 则题目给出的初始匹配就证明了这位王子的公德心
//2。如果题目没有给出这条边 给出的是Ai -> Bk 由于Ai与Bk也可互访 所以存在Bj->Ai->Bk的增广路 也就是说可以建立另外一个匹配A?->Bk
//所以同一个连通分量内部是可以换匹配的

以上来自网络上的分析,已经很清晰

pku1904
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

const int N = 4000+10;

int dfn[N],low[N],f[N],index,n,num;
bool vis[N],instack[N];

vector<int> g[N];
stack<int> st;
int set[N];
void tarjan(int u)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    instack[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        do
        {
            v=st.top();
            st.pop();
            instack[v]=false;
            f[v]=num;
        }while(v!=u);
        num++;
    }
}

void init()
{
    for(int i=0;i<(n<<1);++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(instack,false,sizeof(instack));
    num=index=0;
}

int main()
{
    int k,a;
    while(scanf("%d",&n)==1)
    {
        init();
        for(int i=0;i<n;++i)
        {
            scanf("%d",&k);
            while(k--)
            {
                scanf("%d",&a);
                g[i].push_back(a+n-1);
            }
        }
        for(int i=0;i<n;++i)
        {
            scanf("%d",&a);
            g[a+n-1].push_back(i);
        }
        for(int i=0;i<n;++i)
            if(!vis[i])
                tarjan(i);
        for(int i=0;i<n;++i)
        {
            int k=0;
            for(int j=0;j<g[i].size();++j)
            {
                int u=i,v=g[i][j];
                if(v<n) continue;
                if(f[u]==f[v])
                    set[k++]=v-n+1;
            }
            sort(set,set+k);
            printf("%d",k);
            for(int j=0;j<k;++j)
                printf(" %d",set[j]);
            puts("");
        }
    }
    return 0;
}

 

pku3352 Road Construction   &&  pku3177 Redundant Paths

题意:给定一个无向图,问需要添加的最少边数,将该图边为边双连通图

分析:

//在原图中DFS求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。
//把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,
//边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。有人
//说至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以结果就是(leaf+1)/2。

pku3352 && pku3177
//题目大意是,给定一个连通图,要求添加一些边,使每两个顶点之间都有至少两条不
//相交的路径,求最小需要添加的边数。
//
//很显然,题中要求的图就是一个边双连通图,即边连通度不小于2。我的方法是在原
//图中DFS求出所有的桥,然后删除这些桥边,剩下的每个连通块都是一个双连通子图。
//把每个双连通子图收缩为一个顶点,再把桥边加回来,最后的这个图一定是一棵树,
//边连通度为1。统计出树中度为1的节点的个数,即为叶节点的个数,记为leaf。有人
//说至少在树上添加(leaf+1)/2条边,就能使树达到边二连通,所以结果就是(leaf+1)/2。


#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;

const int N= 1000+10;

int dfn[N],low[N],f[N],index,n,num;
bool vis[N];
int deg[N];
vector<int> g[N];
stack<int> st;

int find(int x)
{
    if(x==f[x])
        return f[x];
    f[x]=find(f[x]);
    return f[x];
}
void Union(int x,int y)
{
    int a=find(x),b=find(y);
    if(a==b) return ;
    f[a]=b;
}
void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    int v;
    bool flag=true;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(v==p && flag) {
            flag=false;
            continue;
        }
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(!(dfn[u]<low[v]))//不是桥,则(u,v) 属于同一个边双连通分量
            {
                Union(u,v);
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
}

void init()
{
    for(int i=0;i<n;i++)
        g[i].clear();
    for(int i=0;i<n;i++)
        f[i]=i;
    memset(vis,false,sizeof(vis));
    memset(deg,0,sizeof(deg));
    num=index=0;
}

int main()
{
    int m;
    int a,b;
    while(scanf("%d %d",&n,&m)==2)
    {
        init();
        while(m--)
        {
            scanf("%d %d",&a,&b);
            --a;--b;
            g[a].push_back(b);
            g[b].push_back(a);
        }
        for(int i=0;i<n;++i)
            if(!vis[i])
                tarjan(i,-1);
        for(int i=0;i<n;++i)
        {
            f[i]=find(i);
            for(int j=0;j<g[i].size();++j)
            {
                f[g[i][j]]=find(g[i][j]);
                if(f[i]!=f[g[i][j]])
                {
                    deg[f[i]]++;
                    deg[f[g[i][j]]]++;
                }
            }
        }
        int ans=0;
        for(int i=0;i<n;++i)
            if(deg[i]==2)
                ans++;
        printf("%d\n",(ans+1)>>1);
    }
    return 0;
}

hdu 3894 By Recognizing These Guys, We Find Social Networks Useful

题意:给定一个无向图,求桥,要求按照给定的顺序输出

分析:给每一条边同时加上编号,若为桥,则记录标号即可,对名字的处理(用字典树给每一个名字标号)

PS:不连通或不存在桥需要特判一下

hdu3894
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
using namespace std;

const int N = 10000+10;
const int kind = 26;
struct edge
{
    int u,v;
    edge(int u=0,int v=0):u(u),v(v){}
}e[N*10];
struct edge1
{
    int v,id;
    edge1(int u=0,int d=0):v(u),id(d){}
};
int dfn[N],low[N],n,index,num,k,ans[N*10];
char str[N][20];
bool vis[N];
vector<edge1> g[N];
struct node
{
    int flag;
    node *next[kind];
    node()
    {
        flag=-1;
        memset(next,NULL,sizeof(next));
    }
};
int insert(char *s,node *root)
{
    node *p=root;
    int i=0;
    while(s[i])
    {
        int in=s[i]-'a';
        if(p->next[in]==NULL)
            p->next[in]=new node();
        p=p->next[in];
        i++;
    }
    if(p->flag==-1)
        p->flag=num++;
    return p->flag;
}

void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i].v;
        if(v==p) continue;
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v])
                ans[k++]=g[u][i].id;
        }
        else low[u]=min(low[u],dfn[v]);
    }
}
void init()
{
    for(int i=0;i<n;++i)
        g[i].clear();
    num=index=k=0;
    memset(vis,false,sizeof(vis));
}

int main()
{
    char s1[20],s2[20];
    int m,a,b;
    int T;
    scanf("%d",&T);
    while(T--)
    {
        node *root=new node();
        init();
        scanf("%d %d",&n,&m);
        for(int i=0;i<m;++i)
        {
            scanf("%s %s",s1,s2);
            a=insert(s1,root);
            b=insert(s2,root);
            e[i]=edge(a,b);
            strcpy(str[a],s1);
            strcpy(str[b],s2);
            g[a].push_back(edge1(b,i));
            g[b].push_back(edge1(a,i));
        }
        int t=0;
        for(int i=0;i<n;i++)
            if(!vis[i])
            {
                t++;
                if(t>1) break;
                tarjan(i,-1);
            }
        if(t!=1) {
            printf("0\n");
            continue;
        }
        printf("%d\n",k);
        if(k==0) continue;
        sort(ans,ans+k);
        for(int i=0;i<k;++i)
            printf("%s %s\n",str[e[ans[i]].u],str[e[ans[i]].v]);
    }
    return 0;
}

hdu3394 Railway

题意:给定一个有N个点,M条边的无向图,求有多少条边没有在环内,有多少条边在至少2个环内。

思路:点双连通,对于每个点双连通,分别求出顶点数,记为a , 和边数,记为b,当a < b ,即顶点数小于边数的时候,则可以证明,这时候连通区域内的边都是clash边,当a == b的时候,则可以得出此时刚好是一个环,每条边都只在正好两个环内,当a > b的时候,这时候连通分量是一棵树,(其实只有两个顶点的时候才可能出现这种情况),此时的边都是没有构成环的边。

 

hdu3394
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;

const int N = 10000+10;

vector<int> g[N];
stack<int> st;

int dfn[N],low[N],n,num,index;
bool vis[N],ins[N];
int sccn[N],ans1,ans2;
void get_ans(int cnt)
{
    int e=0;
    for(int i=0;i<cnt;++i)
    {
        int u=sccn[i];
        for(int j=0;j<g[u].size();++j)
        {
            if(ins[g[u][j]])
                ++e;
        }
    }
    e>>=1;
    if(e>cnt) ans2+=e;
    if(e<cnt) ans1+=e;
    for(int i=0;i<cnt;++i)
        ins[sccn[i]]=false;
}
void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(v==p) continue;
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<=low[v])
            {
                int tmp,cnt=0;
                do
                {
                    tmp=st.top();
                    st.pop();
                    sccn[cnt++]=tmp;
                    ins[tmp]=true;
                }while(tmp!=v);
                ins[u]=true;
                sccn[cnt++]=u;
                get_ans(cnt);
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
}

void init()
{
    for(int i=0;i<n;++i)
        g[i].clear();
    memset(sccn,0,sizeof(sccn));
    memset(vis,false,sizeof(vis));
    memset(ins,false,sizeof(ins));
    num=index=ans1=ans2=0;
}

int main()
{
    int m,a,b;
    while(scanf("%d %d",&n,&m)==2 && (n||m))
    {
        init();
        while(m--)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        for(int i=0;i<n;++i)
            if(!vis[i])
                tarjan(i,-1);
        printf("%d %d\n",ans1,ans2);
    }
    return 0;
}

 pku 1523 SPF

题意:简单的说,就是求割点,同时计算该割点所连接的点双连通分量的数目

pku1523
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;

const int N = 1000+10;

vector<int> g[N];
stack<int> st;

int dfn[N],low[N],n,index;
bool vis[N];
int cut[N];

void init()
{
    for(int i=1;i<=1000;++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(cut,0,sizeof(cut));
    index=0;
}

void tarjan(int u,int p)
{
    int v;
    dfn[u]=low[u]=index++;
    vis[u]=true;
    int sun=0;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v,u);
            ++sun;
            low[u]=min(low[u],low[v]);
            if((p==-1 && sun>1)||(p!=-1 && dfn[u]<=low[v]))
            {
                ++cut[u];
            }
        }
        else if(v!=p)
            low[u]=min(low[u],dfn[v]);
    }
}


int main()
{
    int cas=0;
    int a,b;
    while(scanf("%d",&a) && a)
    {
        init();
        n=0;
        n=max(n,a);
        scanf("%d",&b);
        n=max(n,b);
        g[a].push_back(b);
        g[b].push_back(a);
        while(scanf("%d",&a)&&a)
        {
            scanf("%d",&b);
            n=max(n,max(a,b));
            g[a].push_back(b);
            g[b].push_back(a);
        }
        for(int i=1;i<=n;++i)
            if(!vis[i])
                tarjan(i,-1);
        printf("Network #%d\n",++cas);
        int c=0;
        for(int i=1;i<=n;++i)
        {
            if(cut[i])
            {
                ++c;
                printf("  SPF node %d leaves %d subnets\n",i,cut[i]+1);
            }
        }
        if(c==0)
            puts("  No SPF nodes");
        puts("");
    }
    return 0;
}

 

 pku2553 The Bottom of a Graph

题意:给定一个有向图,如果vertex归属的连通分量的出度为零,则输出该点;输出要从小到大排序

 

pku2553
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;

const int N = 5000+10;

int dfn[N],low[N],f[N],n,num,index;
bool vis[N],instack[N];

vector<int> g[N];
stack<int> st;
int ans[N],deg[N];

void tarjan(int u)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    instack[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i];
        if(!vis[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instack[v])
            low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        do
        {
            v=st.top();
            st.pop();
            instack[v]=false;
            f[v]=num;
        }while(v!=u);
        num++;
    }
}
void init()
{
    for(int i=0;i<=n;++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(instack,false,sizeof(instack));
    num=index=0;
}

int main()
{
    int m;
    int a,b;
    while(scanf("%d",&n)==1 && n)
    {
        scanf("%d",&m);
        init();
        while(m--)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(b);
        }
        for(int i=1;i<=n;++i)
            if(!vis[i])
                tarjan(i);
        memset(deg,0,sizeof(deg));
        for(int i=1;i<=n;++i)
            for(int j=0;j<g[i].size();++j)
                if(f[i]!=f[g[i][j]])
                    deg[f[i]]++;
        int k=0;
        for(int i=0;i<num;i++)
        {
            if(deg[i]==0)
            {
                for(int j=1;j<=n;++j)
                    if(f[j]==i)
                        ans[k++]=j;
            }
        }
        if(k==0)
        {
            puts("");
            continue;
        }    
        sort(ans,ans+k);
        printf("%d",ans[0]);
        for(int i=1;i<k;++i)
            printf(" %d",ans[i]);
        puts("");
    }
    return 0;
}
        

 pku1515 Street Directions

题意:给定一个无向图,尽可能多的将双向边定向,变成单向边,同时保证该图是强连通图

分析:用tarjan 算法,桥必须是双向边,其余边按遍历的方向输出即可,仔细想想确实如此,证明嘛,,,,,,

pku1515
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;

const int N = 1000+10;

struct edge
{
    int v,id;
    edge(int v=0,int id=0):v(v),id(id){}
};
vector<edge> g[N];

int dfn[N],low[N],n,index;
bool vis[N],ins[N*N];

void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i].v;
        if(v==p) continue;
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v])
            {
                printf("%d %d\n",u,v);
                printf("%d %d\n",v,u);
                ins[g[u][i].id]=true;
            }
        }
        else low[u]=min(low[u],dfn[v]);
        if(!ins[g[u][i].id])
        {
            ins[g[u][i].id]=true;
            printf("%d %d\n",u,v);
        }
    }
}
void init()
{
    for(int i=0;i<=n;++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(ins,false,sizeof(ins));
    index=0;
}

int main()
{
    int m,a,b,cas=0;
    while(scanf("%d %d",&n,&m)==2 && (n||m))
    {
        init();
        for(int i=0;i<m;++i)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(edge(b,i));
            g[b].push_back(edge(a,i));
        }
        printf("%d\n\n",++cas);
        for(int i=1;i<=n;++i)
            if(!vis[i])
                tarjan(i,-1);
        puts("#");
    }
    return 0;
}
        

 pku1438 One-way Traffic

题意:与上一题类似,只不过给的边有的是单向边,标记一下就好了(不可能存在单向边的桥,所以不用特判,是桥的话,一定得是双向边)

pku1438
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

const int N = 2000+10;

struct edge
{
    int v,id;
    bool flag;
    edge(int v=0,int id=0,bool f=false):v(v),id(id),flag(f){}
};
vector<edge> g[N];
int dfn[N],low[N],n,index;
bool vis[N],flag[N*N];

void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    int v;
    for(int i=0;i<g[u].size();++i)
    {
        v=g[u][i].v;
        if(v==p) continue;
        if(!vis[v])
        {
            tarjan(v,u);
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v])
            {
                printf("%d %d 2\n",u,v);
                flag[g[u][i].id]=true;
            }
        }
        else low[u]=min(low[u],dfn[v]);
        if(!flag[g[u][i].id] && g[u][i].flag)
        {
            printf("%d %d 1\n",u,v);
            flag[g[u][i].id]=true;
        }
    }
}

void init()
{
    for(int i=0;i<=n;++i)
        g[i].clear();
    memset(vis,false,sizeof(vis));
    memset(flag,false,sizeof(flag));
    index=0;
}

int main()
{
    int a,b,c,m;
    while(scanf("%d %d",&n,&m)==2)
    {
        init();
        for(int i=0;i<m;++i)
        {
            scanf("%d %d %d",&a,&b,&c);
            if(c==1)
                g[a].push_back(edge(b,i,false));
            else {
                g[a].push_back(edge(b,i,true));
                g[b].push_back(edge(a,i,true));
            }
        }
        for(int i=1;i<=n;++i)
            if(!vis[i])
                tarjan(i,-1);
    }
    return 0;
}

 pku3694 Network

题意:给定一个无向图,问没添加一条边之后,该图有多少条割边

分析:tarjan + 并查集 +暴力LCA

         这题里面,并查集的作用至关重要,,首先利用tarjan求出边双连通分量以及割边的总数,之后,,利用tarjan算法时的时间戳暴力求出LCA。我们注意到,对于已经求得的双连通分量缩点之后,如果用桥连接起来,得到的是一棵树,那么每添加一条连通分量以外的边,就会形成一个环,此时,环内所有的桥都被销毁了,所以,我们可以利用求LCA过程中求出经过的哪些桥,同时标记(即将桥直接加到该连通分量中),,,

分别利用vector 和 邻接表运行的结果是好几倍的时间差距。。。

vector 是 3000+ms 而邻接表居然800+ms

pku3694 邻接表存储
#include<iostream>
#include<algorithm>
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
using namespace std;

const int N = 100000+10;
const int M = 500000+10;
struct edge
{
    int v,nex;
}E[M];

int head[N],size;
int dfn[N],low[N],f[N],index,n;
int pre[N],sum;
bool vis[N];
void init()
{
    memset(head,-1,sizeof(head));
    memset(vis,false,sizeof(vis));
    index=sum=0;
    size=0;
    for(int i=0;i<=n;++i)
        f[i]=i;
}

void insert(int u,int v)
{
    E[size].v=v;
    E[size].nex=head[u];
    head[u]=size++;
}

int find(int x)
{
    if(x==f[x])
        return f[x];
    f[x]=find(f[x]);
    return f[x];
}

bool Union(int x,int y)
{
    int a=find(x),b=find(y);
    if(a==b) return false;
    f[a]=b;
    return true;
}

void tarjan(int u,int p)
{
    vis[u]=true;
    dfn[u]=low[u]=index++;
    int v;
    bool flag=true;
    for(int i=head[u];i!=-1;i=E[i].nex)
    {
        v=E[i].v;
        if(v==p && flag){
            flag=false;
            continue;
        }
        if(!vis[v])
        {
            tarjan(v,u);
            pre[v]=u;
            low[u]=min(low[u],low[v]);
            if(dfn[u]<low[v])
                sum++;
            else Union(u,v);
        }
        else low[u]=min(low[u],dfn[v]);
    }
}

void LCA(int u,int v)
{
    while(u!=v)
    {
        while(dfn[u]>dfn[v] && u!=v)//利用时间戳以及pre 数组,回溯到LCA
        {
            if(Union(u,pre[u]))
                sum--;
            else u=pre[u];
        }
        while(dfn[v]>dfn[u] && u!=v)
        {
            if(Union(v,pre[v]))
                sum--;
            else v=pre[v];
        }
    }
}
int main()
{
    int cas=0,m,q,a,b;
    while(scanf("%d %d",&n,&m)==2  && (m||n))
    {
        init();
        while(m--)
        {
            scanf("%d %d",&a,&b);
            insert(a,b);
            insert(b,a);
        }
        tarjan(1,-1);
        printf("Case %d:\n",++cas);
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d %d",&a,&b);
            if(f[a]!=f[b])//加这一句判断快了很多
                LCA(a,b);
            printf("%d\n",sum);
        }
        puts("");
    }
    return 0;
}

 

 hdu4338 Simple Path

题意:给定一个无向图,,进行Q次询问,对于每一次询问,给定起点跟终点,每一个点只能经过一次,问有多少个点是不可能经过的

分析:点数N 和边数M 都十分的庞大,,首先应该想到的就是缩点了,将每一个点双连通分量缩成一个点,问题是割点可以同时属于多个块,所以可以将割点再单独建立一个点,然后在块与割点直接连边,,得到的是一颗树,,这样隐约可以感觉到了,LCA 转RMQ ,,

具体可以这位大牛的博文http://blog.sina.com.cn/s/blog_7270d7f901017l3o.html

hdu4338
#include<iostream>
#include<algorithm>
#include<math.h>
#include<stack>
#include<vector>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
using namespace std;

const int N = 100000+10;
const int M = 400000+10;
#pragma comment(linker, "/STACK:1024000000,1024000000") 
struct Edge
{
    int v,nex;
}E1[M],E2[M];
stack<int> st;
int dfn[N],low[N],index,n,scc[N];
bool vis[N],cut[N];
int head1[N],size1,head2[N],size2;
int f[N];
int dis[N],num,depth[N];

int dp[N*2][20],F[N*2],B[N*2],pos[N*2],bn,belong[N];
//F[]保存dfs产生的欧拉序列,dis保存每个节点到根的距离,
//B[]保存与F[]对应的节点的深度,pos[]保存每一个节点在欧拉序列在第一次出现的位置
vector<int> g[N];
void init()
{
    for(int i=0;i<n;++i)
        g[i].clear();
    memset(head1,-1,sizeof(head1));
    memset(head2,-1,sizeof(head2));
    memset(vis,false,sizeof(vis));
    memset(scc,0,sizeof(scc));
    memset(cut,false,sizeof(cut));
    memset(f,-1,sizeof(f));
    memset(belong,-1,sizeof(belong));
    size1=size2=index=num=0;
}
void insert1(int u,int v)
{
    E1[size1].v=v;
    E1[size1].nex=head1[u];
    head1[u]=size1++;
}
void insert2(int u,int v)
{
    E2[size2].v=v;
    E2[size2].nex=head2[u];
    head2[u]=size2++;
}


void tarjan(int u,int p)
{
    dfn[u]=low[u]=index++;
    vis[u]=true;
    st.push(u);
    int v,son=0,flag=true;
    for(int i=head1[u];i!=-1;i=E1[i].nex)
    {
        v=E1[i].v;
        if(v==p && flag)
        {
            flag=false;
            continue;
        }
        if(!vis[v])
        {
            tarjan(v,u);
            son++;
            low[u]=min(low[u],low[v]);
            if(dfn[u]<=low[v])
            {
                int x;
                do
                {
                    x=st.top();
                    st.pop();
                    g[num].push_back(x);
                }while(x!=v);
                g[num].push_back(u);//保存每一个块
                num++;
            }
            if((p==-1 && son>1) || (p!=-1 && dfn[u]<=low[v])) 
                cut[u]=true;
        }
        else low[u]=min(low[u],dfn[v]);
    }
}

void dfs(int cur,int deep,int len,int b)
{
    vis[cur]=true;
    dis[cur]=len;
    depth[cur]=deep;
    F[bn]=cur;
    B[bn]=deep;
    pos[cur]=bn++;
    belong[cur]=b;
    for(int i=head2[cur];i!=-1;i=E2[i].nex)
    {
        int v=E2[i].v;
        if(!vis[v])
        {
            dfs(v,deep+1,len+scc[v],b);
            F[bn]=cur;
            B[bn++]=deep;
        }
    }
}
void init_RMQ()
{
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=bn;++i)
        dp[i][0]=i;
    for(int j=1;j<=log((double)(bn+1))/log(2.0);++j)
    {
        int limit=bn+1-(1<<j);
        for(int i=1;i<=limit;++i)
        {
            int x=dp[i][j-1];
            int y=dp[i+(1<<(j-1))][j-1];
            dp[i][j]=B[x]<B[y]?x:y;
        }
    }
}
int RMQ(int a,int b)//返回的是下标
{
    if(a>b){
        swap(a,b);
    }
    int k=(int)(log((double)(b-a+1))/log(2.0));
    int x=dp[a][k];
    int y=dp[b+1-(1<<k)][k];
    return B[x]<B[y]?x:y;
}


int main()
{
    int m,q,cas=0;
    int a,b;
    while(scanf("%d %d",&n,&m)==2)
    {
        init();
        for(int i=0;i<m;++i)
        {
            scanf("%d %d",&a,&b);
            insert1(a,b);
            insert1(b,a);
        }
        for(int i=0;i<n;++i)
            if(!vis[i])
                tarjan(i,-1);
        int k=0;
        for(int i=0;i<n;++i)
        {
            if(cut[i])
            {
                f[i]=k;
                scc[k]=1;
                k++;
            }
        }
        for(int i=0;i<num;++i)
        {
            for(int j=0;j<g[i].size();++j)
            {
                int v=g[i][j];
                if(cut[v])//块与割点之间建边
                {
                    insert2(f[v],k);
                    insert2(k,f[v]);
                }
                else f[v]=k;
            }
            scc[k]=g[i].size();
            k++;
        }
        memset(vis,false,sizeof(vis));
        bn=1;
        for(int i=0;i<k;++i)
            if(!vis[i])
                dfs(i,0,scc[i],i);
        bn--;
        init_RMQ();
        scanf("%d",&q);
        printf("Case #%d:\n",++cas);
        while(q--)
        {
            scanf("%d %d",&a,&b);
            if(a==b) {
                printf("%d\n",n-1);
                continue;
            }
            int u=f[a],v=f[b];
            if(u==-1 || v==-1)//既不是割点也不是块
            {
                printf("%d\n",n);
                continue;
            }
            if(belong[u]!=belong[v])//不连通,即不属于同一个连通分量
            {
                printf("%d\n",n);
                continue;
            }
            int t=RMQ(pos[u],pos[v]);
            int tmp=dis[u]+dis[v]-2*dis[F[t]]+scc[F[t]];
            int l=depth[u]+depth[v]-2*depth[F[t]];
            printf("%d\n",n-(tmp-l));
        }
        puts("");
    }
    return 0;
}

 

 

转载于:https://www.cnblogs.com/nanke/archive/2012/08/11/2633515.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值