简单图论练习题OJ

问题 A: 最短路径问题

时间限制: 1 Sec  内存限制: 128 MB

题目描述

给定有向图 G,以及原点 S,请求出原点到所有点的最短路径。 

输入

输入文件的第一行包含两个整数 n, m,代表图中的顶点数和边数。

接下来 m 行,每行三个整数 u, v, w,代表一条从 u 指向 v,权值 为 w 的边。

最后一行为一个整数 S。 

输出

输出 n 个整数,依次代表 S 到 1, 2, . . . , n 的最短距离, S 到自己的距 离定义为 0,对于无法从 S 到达的点输出 -1。整数用一个空格隔开。 

样例输入

3 3 1 2 1 2 3 2 1 3 1 1

样例输出

0 1 1

提示

对于100%的数据,n ≤ 100000,m ≤ 800000,0 ≤ w ≤ 1000,请使用 dijkstra 算法。 

Dijkstra 算法的每一次迭代分为两个步骤:选出当前距离最小的点和 从该点更新其他点的当前最小距离。很明显,第一步可以利用堆来选择, 时间复杂度降至 log(n),而第二步则可能会更改堆中的某些点的权值,这 时有两种处理方法,第一是额外记录每个点在堆中的位置并维护;第二是 直接将一个新结点插入堆中,因为新结点比老结点的权值更小一定在老结 点之前到达堆顶,所以这样做没有问题,当以后碰到堆顶结点为最短距离 已确定的结点,简单地将其丢弃即可,时间复杂度仅上升一个常数,是一 个不错的偷懒方法。 

dijkstra+堆

#include<cstdio>
#include<queue>
using namespace std;
int read()
{
    int ret=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret; 
}
  
const int N=1e6+5; 
int n,m,d[N],s;
bool fl[N];
int cnt,he[N],to[N],nxt[N],w[N];
  
inline void add(int u,int v,int k)
{
    to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt,w[cnt]=k; 
}
  
struct NA{
    int id,x;
};
bool operator >(NA i,NA j)
{
    return i.x>j.x;
} 
priority_queue<NA,vector<NA>,greater<NA> >q;
  
int main() 
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read(),k=read();
        add(u,v,k); 
    } 
    s=read();
    for(int i=1;i<=n;i++) d[i]=2e9;
    q.push((NA){s,d[s]=0});
    for(int i=1;i<=n;i++)
    {
        while(!q.empty()&&fl[q.top().id]) q.pop();
        if(q.empty()) break;
        int u=q.top().id; fl[u]=1;
        q.pop();
        for(int e=he[u];e;e=nxt[e])
        {
            int v=to[e];
            if(!fl[v]&&d[v]>d[u]+w[e]) 
                q.push((NA){v,d[v]=d[u]+w[e]});
        }
    }
    for(int i=1;i<n;i++)
        if(d[i]!=2e9) printf("%d ",d[i]);
            else printf("-1 ");
    if(d[n]!=2e9) printf("%d",d[n]);
        else printf("-1");
    return 0;
}

问题 B: 最短路径问题(二)

时间限制: 1 Sec  内存限制: 128 MB

题目描述

给定有向图 G,以及原点 S,请求出原点到所有点的最短路径。 

输入

输入文件的第一行包含两个整数 n, m,代表图中的顶点数和边数。

接下来 m 行,每行三个整数 u, v, w,代表一条从 u 指向 v,权值 为 w 的边。 

最后一行为一个整数 S。 

输出

若图中存在负权环,则输出一行“ERROR”。

否则输出 n 个整数,依次代表 S 到 1, 2, . . . , n 的最短距离, S 到自己 的距离定义为 0,对于无法从 S 到达的点输出 -1。整数用一个空格隔开。 

样例输入

样例输出

提示
 

对于所有数据, n ≤ 1000, m ≤ 100000, −1000 ≤ w ≤ 1000。 

SPFA模拟题

#include<cstdio> 
using namespace std; 
int read() 
{ 
    int ret=0; char ch=getchar(); bool f=0; 
    while(ch<'0'||ch>'9')  
    { 
        if(ch=='-') f=1; 
        ch=getchar(); 
    } 
    while(ch>='0'&&ch<='9') 
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar(); 
    return f?-ret:ret; 
} 
    
const int N=1e6+5; 
int n,m,d[N],s,l,r,q[N],num[N]; 
int cnt,he[N],to[N],nxt[N],w[N]; 
    
inline void add(int u,int v,int k) 
{ 
    to[++cnt]=v,nxt[cnt]=he[u],w[cnt]=k,he[u]=cnt; 
} 
    
int main() 
{ 
    n=read(),m=read(); 
    for(int i=1;i<=m;i++) 
    { 
        int u=read(),v=read(),k=read(); 
        add(u,v,k); 
    } 
    s=read();  
    for(int i=1;i<=n;i++) d[i]=1e9; 
    l=1,r=1,q[1]=s; d[s]=0; num[s]=1;  
    bool f=0;
    while(l<=r) 
    { 
        for(int e=he[q[l]];e;e=nxt[e]) 
        { 
            int v=to[e]; 
            if(d[v]>d[q[l]]+w[e])  
            { 
                d[v]=d[q[l]]+w[e],num[v]++;  
                if(num[v]==n)  
                { 
                    printf("ERROR"); 
                    f=1; break;
                } 
                q[++r]=v; 
            } 
        } 
        if(f) break;
        l++;
    } 
    if(!f)
    {
    for(int i=1;i<n;i++) 
        if(d[i]!=1e9) printf("%d ",d[i]); 
            else printf("-1 "); 
    if(d[n]!=1e9) printf("%d",d[n]); 
        else printf("-1"); 
    }
    return 0; 
}  

问题 C: 最小圈

时间限制: 1 Sec  内存限制: 128 MB

题目描述

给定一个带权无向图 G = (V, E),请求出其中的最小圈。圈定义为 顶点序列 (v1,v2,...,vp,vp+1),满足 vi = vj(i = j), v1 = vp+1,且边 (vi, vi+1) ∈ E。 

输入

第一行为两个整数 n,m,代表图的顶点数和边数。

接下来 m 行,每行三个整数 u,v,w,描述一条连接 u 和 v,权值为 w 的边。 

输出

输出一个整数,即图中的最小圈。数据保证图中至少存在一个圈。 

样例输入

6 7 1 2 1 2 4 2 4 6 3 5 6 4 3 5 5 3 4 2 1 3 6

样例输出

11

提示

对于30%的数据n ≤ 30

对于 60% 的数据, n ≤ 100

对于 100% 的数据, n ≤ 400, w ≤ 100000

利用 Floyd 算法,求得环中的点最大编号为 1, 2, . . . , n 时的最小环。 

模板题

#include<cstdio>
#include<iostream>
using namespace std;
int read()
{
    int ret=0; char ch=getchar(); bool f=0;
    while(ch<'0'||ch>'9') 
    {
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return f?-ret:ret;
}
  
const int N=405;
int n,m,f[N][N],a[N][N][2],ans;
  
int main()
{
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) a[i][j][0]=a[i][j][1]=f[i][j]=6e8;
    for(int i=1;i<=n;i++) f[i][i]=0;
    ans=2e9;
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read(),w=read();
        f[u][v]=f[v][u]=min(f[u][v],w);
        if(a[u][v][0]!=6e8) a[u][v][0]=a[v][u][0]=w;
            else if(a[u][v][1]!=6e8) a[u][v][1]=a[v][u][1]=w;
                else
                {
                    if(a[u][v][0]>w) 
                        a[u][v][1]=a[v][u][1]=a[u][v][1],a[u][v][0]=a[v][u][0]=w;
                    else a[u][v][1]=a[v][u][1]=min(a[u][v][1],w);
                }
        ans=min(ans,a[u][v][0]+a[u][v][1]);
    } 
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            {
                if(i!=j&&f[i][j]!=6e8&&a[i][k][0]!=6e8&&a[j][k][0]!=6e8) ans=min(ans,f[i][j]+a[i][k][0]+a[k][j][0]);
                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);
            }
    printf("%d",ans);
    return 0;
} 

问题 D: 公路修建

时间限制: 1 Sec  内存限制: 128 MB

题目描述

有 n 个城市需要用道路连接起来,现在有 m 条道路的修建方案,每 个方案的实施可以带来一定的利润,你的任务是确定实施的方案,使得任 意两个城市都连通,并且获得的利润最大。 

输入

第一行为两个整数 n, m,含义如题中所述。n ≤ 10000, m ≤ 200000。 

接下来有 m 行,每行三个整数 u, v, w,代表在 u 和 v 之间修建一 条道路可以获得 w 的利润。注意,修建一条道路可能会导致亏损,这时我 们用负的利润表示。 

输出

输出一行,代表能够获得的最大利益。 

样例输入

3 3 1 2 -1 2 3 -2 1 3 -1

样例输出

-2

>=0的边全部加上,其余的最大生成树处理

#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
int read()
{
    int ret=0; char ch=getchar(); bool f=0;
    while(ch<'0'||ch>'9') 
    {
        if(ch=='-') f=1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return f?-ret:ret;
}
  
const int N=1e6+5;
int n,m,ff[N];
ll ans;
struct NA{
    int u,v,w;
}e[N];
bool cmp(NA i,NA j)
{
    return i.w>j.w;
}
  
int find(int x)
{
    return x==ff[x]?x:ff[x]=find(ff[x]);
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
        e[i].u=read(),e[i].v=read(),e[i].w=read();
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=n;i++) ff[i]=i;
    for(int i=1;i<=m;i++)
    if(e[i].w>=0)
    {
        int u=e[i].u,v=e[i].v;
        int f1=find(u),f2=find(v);
        ans+=e[i].w,ff[f1]=f2;
    } else
    {
        int u=e[i].u,v=e[i].v;
        int f1=find(u),f2=find(v);
        if(ff[f1]!=f2) ans+=e[i].w,ff[f1]=f2;   
    }
    printf("%lld",ans);
    return 0;
}

问题 E: 刻录光盘

时间限制: 1 Sec  内存限制: 128 MB

题目描述

在 JSOI2005 夏令营快要结束的时候,很多营员提出来要把整个夏令 营期间的资料刻录成一张光盘给大家,以便大家回去后继续学习。组委会 觉得这个主意不错!可是组委会一时没有足够的空光盘,没法保证每个人 都能拿到刻录上资料的光盘,又来不及去买了,怎么办呢?!

组委会把这个难题交给了 LHC,LHC 分析了一下所有营员的地域关 系,发现有些营员是一个城市的,其实他们只需要一张就可以了,因为一 个人拿到光盘后,其他人可以带着 U 盘之类的东西去拷贝啊!

可是,LHC 调查后发现,由于种种原因,有些营员并不是那么的合 作,他们愿意某一些人到他那儿拷贝资料,当然也可能不愿意让另外一些 人到他那儿拷贝资料,这与我们 JSOI 宣扬的团队合作精神格格不入!

现在假设总共有 N 个营员(2<=N<=2000),每个营员的编号为 1 N。 LHC 给每个人发了一张调查表,让每个营员填上自己愿意让哪些人到他那 儿拷贝资料。当然,如果 A 愿意把资料拷贝给 B,而 B 又愿意把资料拷 贝给 C,则一旦 A 获得了资料,则 B,C 都会获得资料。

现在,请你编写一个程序,根据回收上来的调查表,帮助 LHC 计算 出组委会至少要刻录多少张光盘,才能保证所有营员回去后都能得到夏令 营资料? 

输入

先是一个数 N,接下来的 N 行,分别表示各个营员愿意把自己获得的 资料拷贝给其他哪些营员。即输入数据的第 i+1 行表示第 i 个营员愿意把 资料拷贝给那些营员的编号,以一个 0 结束。如果一个营员不愿意拷贝资 料给任何人,则相应的行只有 1 个 0,一行中的若干数之间用一个空格隔 开。 

输出

一个正整数,表示最少要刻录的光盘数。 

样例输入

5 2 4 3 0 4 5 0 0 0 1 0

样例输出

1

提示

tarjan缩点后统计度为0的结点

#include<cstdio>
#include<iostream>
using namespace std;
int read()
{
    int ret=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret;
}
  
const int N=2005,M=4000005;
int n,m,d[N],ans;
int cnt,he[M],to[M],nxt[M];
struct NA{
    int u,v;
}e[M];
int sgn,dfn[N],low[N],top,st[N],col,co[N];
  
inline void add(int u,int v)
{
    to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
  
void tar(int u)
{
    dfn[u]=low[u]=++sgn;
    st[++top]=u;
    for(int e=he[u];e;e=nxt[e])
    {
        int v=to[e];
        if(!dfn[v]) 
            tar(v),low[u]=min(low[u],low[v]);
        else if(!co[v]) 
            low[u]=min(low[u],dfn[v]);
    }
    if(low[u]==dfn[u])
    {
        co[u]=++col;
        while(top&&st[top]!=u)
            co[st[top--]]=col;
        top--;
    }
}
  
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        while(x)
            e[++m].u=i,e[m].v=x,
            add(i,x),x=read();
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i]) tar(i);
    for(int i=1;i<=m;i++)
        if(co[e[i].u]!=co[e[i].v]) d[co[e[i].v]]++;
    ans=0; 
    for(int i=1;i<=col;i++)
        if(!d[i]) ans++;
    printf("%d",ans);
    return 0;
}

问题 F: 割点和桥

时间限制: 1 Sec  内存限制: 128 MB

题目描述

给定连通无向图 G,请求出 G 中的割点和桥的个数。 

输入

第一行两个整数 n,m,代表图的顶点数和边数。 接下来 m 行,每行两个整数 u, v,描述一条无向边。 N ≤ 50000, m ≤ 200000 

输出

输出两个整数,先输出割点的数量和再输出桥的数量,用一个空格隔 开。 

样例输入

4 5 1 2 1 2 2 3 2 3 3 4

样例输出

2 1

#include<cstdio>
#include<iostream>
using namespace std;
int read()
{
    int ret=0; char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
        ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();
    return ret;
}
  
const int N=1e6+5;
int n,m,num[N],ans;
int cnt,he[N],to[N],nxt[N];
int sgn,low[N],dfn[N];
  
inline void add(int u,int v)
{
    to[++cnt]=v,nxt[cnt]=he[u],he[u]=cnt;
}
  
void tar1(int u,int r)
{
    low[u]=dfn[u]=++sgn;
    int ch=0;
    for(int e=he[u];e;e=nxt[e])
    {
        int v=to[e];
        if(!dfn[v]) 
        {
            tar1(v,r); low[u]=min(low[u],low[v]);
            if(u!=r&&low[v]>=dfn[u]) num[u]=1;
            if(u==r) ch++;
        }
        low[u]=min(low[u],dfn[v]);
    }
    if(u==r&&ch>=2) num[u]=1; 
}
  
void tar2(int fa,int u)
{
    low[u]=dfn[u]=++sgn;
    for(int e=he[u];e;e=nxt[e])
    {
        int v=to[e];
        if(v!=fa)
        {
            if(!dfn[v])
            {
                tar2(u,v); low[u]=min(low[u],low[v]);
                if(dfn[u]<low[v]) ans++; 
            }
            low[u]=min(low[u],dfn[v]);
        } else fa=0;
    }
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        add(u,v); add(v,u);
    }
    tar1(1,1);
    for(int i=1;i<=n;i++) 
        if(num[i]) ans++;
    printf("%d ",ans);
    sgn=ans=0; 
    for(int i=1;i<=n;i++)
        dfn[i]=low[i]=0;
    tar2(0,1);
    printf("%d",ans);
    return 0;
} 

猜你喜欢

转载自blog.csdn.net/YYHS_WSF/article/details/83377720