1. 程式人生 > >簡單圖論練習題OJ

簡單圖論練習題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;
}