NOIP2018 复赛 图论模板集锦(精)


[图论模板]

最短路径问题(无负边权)

  Time Limit: 1 Sec
  Memory Limit: 128 MB

Description###

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

Input###

   输入文件的第一行包含两个整数 n, m,代表图中的顶点数和边数。
接下来 m 行,每行三个整数 u, v, w,代表一条从 u 指向 v,权值 为 w 的边。
最后一行为一个整数 S。
 

Output###

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

Sample Input###

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

Sample Output###

  0 1 1
  

HINT

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


AC代码

#include <cstdio>
#include <cstring>
#include <queue>
#define pa pair<int,int>
using namespace std;
const int N=1e5+5,inf=1e9;
int n,m;
inline int read(){
	int x=0,f=1;char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct edge{
    int to,val,next;
}e[N<<3];
int cnt_edge,last[N];
inline void add_edge(int u,int v,int w){
    e[++cnt_edge]=(edge){v,w,last[u]};last[u]=cnt_edge;
}
int dis[N];bool vis[N];
priority_queue<pa,vector<pa>,greater<pa> >q;
void Dijkstra(){
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)dis[i]=inf;
    dis[1]=0;q.push(make_pair(0,1));
    while(!q.empty()){
        int u=q.top().second;q.pop();
        vis[u]=1;
        for(int i=last[u];i;i=e[i].next){
            int v=e[i].to;
            if(vis[v])continue;
            if(dis[v]>dis[u]+e[i].val){
                dis[v]=dis[u]+e[i].val;
                q.push(make_pair(dis[v],v));
            }
        }
    }
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        add_edge(u,v,w);
    }
    Dijkstra();
    for(int i=1;i<n;i++)
        if(dis[i]==inf)printf("-1 ");
        else printf("%d ",dis[i]);
    if(dis[n]==inf)printf("-1\n");
    else printf("%d\n",dis[n]);
    return 0;
}

最短路径问题(二)(有负边权)

  Time Limit: 1 Sec
  Memory Limit: 128 MB

Description###

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

Input###

   输入文件的第一行包含两个整数 n, m,代表图中的顶点数和边数。
接下来 m 行,每行三个整数 u, v, w,代表一条从 u 指向 v,权值 为 w 的边。
最后一行为一个整数 S。
 

Output###

  若图中存在负权环,则输出一行“ERROR”。
否则输出 n 个整数,依次代表 S 到 1, 2, . . . , n 的最短距离, S 到自己 的距离定义为 0,对于无法从 S 到达的点输出 -1。整数用一个空格隔开。
 

Sample Input###

Sample Output###

HINT

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


AC代码

#include <cstdio>
#include <cstdlib>
#include <queue>
using namespace std;
const int N=1e3+5,inf=1e9;
int n,m;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct edge{
    int to,val,next;
}e[100005];
int cnt_edge,last[N];
void add_edge(int u,int v,int w){
    e[++cnt_edge]=(edge){v,w,last[u]};last[u]=cnt_edge;
}
queue<int> q;
int dis[N],ins[N];
bool inq[N];
void SPFA(){
    for(int i=1;i<=n;i++)dis[i]=inf;
    dis[1]=0;
    q.push(1);
    inq[1]=1;ins[1]=1;
    while(!q.empty()){
        int u=q.front();q.pop();
        inq[u]=0;
        for(int i=last[u];i;i=e[i].next){
            int v=e[i].to;
            if(dis[v]>dis[u]+e[i].val){
                dis[v]=dis[u]+e[i].val;
                if(!inq[v]){
                    q.push(v);
                    inq[v]=1;
                }
                ins[v]++;
                if(ins[v]==n){
                    puts("ERROR");
                    exit(0);
                }
            }
        }
    }
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        add_edge(u,v,w);
    }
    SPFA();
    for(int i=1;i<n;i++)
        if(dis[i]==inf)printf("-1 ");
        else printf("%d ",dis[i]);
    if(dis[n]==inf)printf("-1\n");
    else printf("%d\n",dis[n]);
    return 0;
}

最小圈

  Time Limit: 1 Sec
  Memory Limit: 128 MB

Description###

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

Input###

   第一行为两个整数 n,m,代表图的顶点数和边数。
   接下来 m 行,每行三个整数 u,v,w,描述一条连接 u 和 v,权值为 w 的边。
 

Output###

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

Sample Input###

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

Sample Output###

  11
  

HINT

  对于 100% 的数据, n ≤ 400, w ≤ 100000
  利用 Floyd 算法,求得环中的点最大编号为 1, 2, . . . , n 时的最小环。


AC代码

#include <cstdio>
#include <algorithm>
using namespace std;
const int inf=1e8;
int n,m,mn;
int mp[405][405],dis[405][405];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            dis[i][j]=dis[j][i]=mp[i][j]=mp[j][i]=inf;
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        dis[u][v]=dis[v][u]=mp[u][v]=mp[v][u]=min(dis[u][v],w);
    }
    mn=inf;
    for(int k=1;k<=n;k++){
        for(int i=1;i<k;i++)
            for(int j=i+1;j<k;j++)
                mn=min(mn,dis[i][j]+mp[i][k]+mp[k][j]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    }
    printf("%d\n",mn);
    return 0;
}

公路修建(有坑 有负边权 的最大生成树)

  Time Limit: 1 Sec
  Memory Limit: 128 MB

Description###

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

Input###

   第一行为两个整数 n, m,含义如题中所述。n ≤ 10000, m ≤ 200000。
接下来有 m 行,每行三个整数 u, v, w,代表在 u 和 v 之间修建一 条道路可以获得 w 的利润。注意,修建一条道路可能会导致亏损,这时我 们用负的利润表示。
 

Output###

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

Sample Input###

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

Sample Output###

  -2
  

HINT


AC代码

#include <cstdio>
#include <algorithm>
using namespace std;
int n,m,cnt,ans,fa[10005];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct edge{
    int u,v,w;
}e[200005];
bool cmp(edge a,edge b){
    return a.w>b.w;
}
int find(int x){
    while(x!=fa[x])x=fa[x]=fa[fa[x]];
    return x;
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        fa[i]=i;
    for(int i=1;i<=m;i++){
        int u=read(),v=read(),w=read();
        if(w>=0){
            ans+=w;
            int fx=find(u);
            int fy=find(v);
            if(fx!=fy)fa[fx]=fy;
        }else e[++cnt]=(edge){u,v,w};
    }
    sort(e+1,e+cnt+1,cmp);
    for(int i=1;i<=cnt;i++){
        int fx=find(e[i].u);
        int fy=find(e[i].v);
        if(fx!=fy){
            ans+=e[i].w;
            fa[fx]=fy;
        }
    }
    printf("%d\n",ans);
    return 0;
}

1504: 刻录光盘 (求强连通分量)

  Time Limit: 1 Sec
  Memory Limit: 128 MB

Description###

   在 JSOI2005 夏令营快要结束的时候,很多营员提出来要把整个夏令 营期间的资料刻录成一张光盘给大家,以便大家回去后继续学习。组委会 觉得这个主意不错!可是组委会一时没有足够的空光盘,没法保证每个人 都能拿到刻录上资料的光盘,又来不及去买了,怎么办呢?!
组委会把这个难题交给了 LHC,LHC 分析了一下所有营员的地域关 系,发现有些营员是一个城市的,其实他们只需要一张就可以了,因为一 个人拿到光盘后,其他人可以带着 U 盘之类的东西去拷贝啊!
可是,LHC 调查后发现,由于种种原因,有些营员并不是那么的合 作,他们愿意某一些人到他那儿拷贝资料,当然也可能不愿意让另外一些 人到他那儿拷贝资料,这与我们 JSOI 宣扬的团队合作精神格格不入!
现在假设总共有 N 个营员(2<=N<=2000),每个营员的编号为 1 N。 LHC 给每个人发了一张调查表,让每个营员填上自己愿意让哪些人到他那 儿拷贝资料。当然,如果 A 愿意把资料拷贝给 B,而 B 又愿意把资料拷 贝给 C,则一旦 A 获得了资料,则 B,C 都会获得资料。
现在,请你编写一个程序,根据回收上来的调查表,帮助 LHC 计算 出组委会至少要刻录多少张光盘,才能保证所有营员回去后都能得到夏令 营资料?
 

Input###

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

Output###

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

Sample Input###

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

Sample Output###

  1
  

HINT


AC代码

#include <cstdio>
#include <algorithm>
using namespace std;
const int N=2005;
int n,m,num,ans;
int U[N*N],V[N*N],in[N];
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct edge{
    int to,next;
}e[N*N],_e[N*N];
int cnt_edge,last[N];
void add_edge(int u,int v){
    e[++cnt_edge]=(edge){v,last[u]};last[u]=cnt_edge;
}
int _cnt_edge,_last[N];
void _add_edge(int u,int v){
    _e[++_cnt_edge]=(edge){v,_last[u]};_last[u]=_cnt_edge;
}
int Time,top,col_num,col[N],dfn[N],low[N],q[N];
bool ins[N];
void Tarjan(int u){
    dfn[u]=low[u]=++Time;
    q[++top]=u;ins[u]=1;
    for(int i=last[u];i;i=e[i].next){
        int v=e[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]){
        ++col_num;
        while(q[top]!=u){
            col[q[top]]=col_num;
            ins[q[top]]=0;
            top--;
        }
        col[q[top]]=col_num;
        ins[q[top]]=0;
        top--;
    }
}
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        int x=read();
        while(x!=0){
            ++num;
            U[num]=i;
            V[num]=x;
            add_edge(i,x);
            x=read();
        }
    }
    for(int i=1;i<=n;i++)
        if(!dfn[i])Tarjan(i);
    for(int i=1;i<=num;i++)
        if(col[U[i]]!=col[V[i]])
        in[col[V[i]]]++;
    for(int i=1;i<=col_num;i++)
        if(!in[i])ans++;
    printf("%d\n",ans);
    return 0;
}

割点和桥

  Time Limit: 1 Sec
  Memory Limit: 128 MB

Description###

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

Input###

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

Output###

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

Sample Input###

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

Sample Output###

  2 1
  

HINT


AC代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N=5e4+5;
int n,m;
inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
struct edge{
    int to,next;
}e[400005];
int cnt_edge=1,last[N];
inline void add_edge(int u,int v){
    e[++cnt_edge]=(edge){v,last[u]};last[u]=cnt_edge;
}
struct _edge{
    int from,to;
};
int Time,dfn[N],low[N];
void init(){
    Time=0;
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
}
bool isCut_point[N];
void Tarjan_cut_point(int u){
    dfn[u]=low[u]=++Time;
    int child=0;
    for(int i=last[u];i;i=e[i].next){
        int v=e[i].to;
        if(!dfn[v]){
            Tarjan_cut_point(v);
            low[u]=min(low[u],low[v]);
            child++;
            if(u!=1 && low[v]>=dfn[u])isCut_point[u]=1;
        }else
            low[u]=min(low[u],dfn[v]);
    }
    if(u==1 && child>1)isCut_point[u]=1;
}
vector<_edge> bridge;
void Tarjan_bridge(int u,int from){
    dfn[u]=low[u]=++Time;
    for(int i=last[u];i;i=e[i].next){
        int v=e[i].to;
        if(i==(from^1))continue;
        if(!dfn[v]){
            Tarjan_bridge(v,i);
            low[u]=min(low[u],low[v]);
            if(low[v]>dfn[u])bridge.push_back((_edge){u,v});
        }else
            low[u]=min(low[u],dfn[v]);
    }
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=m;i++){
        int u=read(),v=read();
        add_edge(u,v);
        add_edge(v,u);
    }
    init();
    Tarjan_cut_point(1);
    init();
    Tarjan_bridge(1,0);
    int Cut_point=0;
    for(int i=1;i<=n;i++)
        if(isCut_point[i])Cut_point++;
    printf("%d %d\n",Cut_point,(int)bridge.size());
    return 0;
}

  未完待续


  作者:skl_win
  出处:https://www.cnblogs.com/shaokele/
  本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

posted @ 2018-10-24 16:15  skl_win  阅读(327)  评论(0编辑  收藏  举报
Live2D