天天看點

tarjan算法題目練習(長期更新)

P2002 消息擴散

題目描述

有n個城市,中間有單向道路連接配接,消息會沿着道路擴散,現在給出n個城市及其之間的道路,問至少需要在幾個城市釋出消息才能讓這所有n個城市都得到消息。

tarjan縮點(因為一個強連通分量内的點隻要又一個傳到了其他點都傳到了,是以可以縮成一個點),然後重建立圖,統計out[i]為0的點的數量

#include <iostream>
#include <algorithm>
#include <set>
#include <cstring>
using namespace std;
const int maxn = 1e5+5;
set<int> st[maxn];
struct Edge{
    int u,v,nxt;
}edge[500005];
int head[maxn],tot;
inline void init(){
    memset(head,-1,sizeof(head));
    tot = 0;
}
inline void addedge(int u,int v){
    edge[++tot] = {u,v,head[u]};
    head[u] = tot;
}
bool vis[maxn];
int dfn[maxn],low[maxn],dfnnum,sta[maxn],stalen,num,color[maxn],out[maxn];
void tarjan(int u){
    dfn[u] = low[u] = ++dfnnum;
    sta[stalen++] = u,vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        Edge &e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
    }
    if(dfn[u]==low[u]){
        ++num;  int cur;
        do{
            cur = sta[--stalen];
            vis[cur] = false;
            color[cur] = num;
        }while(u!=cur);
    }
}

int main()
{
    init();
    int n,m;    cin >> n >> m;
    for(int i = 0; i < m; ++i){
        int u,v;    cin >> u >> v;
        if(u==v)    continue;
        addedge(u,v);
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i]) tarjan(i);
    }
    for(int i = 1; i <= n; ++i){
        for(int j = head[i]; ~j; j = edge[j].nxt){
            int u = color[i],v = color[edge[j].v];
            if(u!=v && !st[u].count(v)){
                out[v]++;
                st[u].insert(v);
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= num; ++i){
        if(!out[i]) ans++;
    }
    cout << ans << endl;
    return 0;
}
           

P2169 正規表達式

題目背景

小Z童鞋一日意外的看到小X寫了一個正規表達式的進階程式,這個正規表達式程式僅僅由字元“0”,“1”,“.”和“*”構成,但是他能夠比對出所有在OJ上都AC的程式的核心代碼!小Z大為頗感好奇,于是他決定入侵小X的電腦上去獲得這個正規表達式的進階程式。

題目描述

在Internet網絡中的每台電腦并不是直接一對一連通的,而是某些電腦之間存在單向的網絡連接配接,也就是說存在A到B的連接配接不一定存在B到A的連接配接,并且有些連接配接傳輸速度很快,有些則很慢,是以不同連接配接傳輸所花的時間是有大有小的。另外,如果存在A到B的連接配接的同時也存在B到A的連接配接的話,那麼A和B實際上處于同一區域網路内,可以通過本地傳輸,這樣花費的傳輸時間為0。

現在小Z告訴你整個網絡的構成情況,他希望知道從他的電腦(編号為1),到小X的電腦(編号為n)所需要的最短傳輸時間。

很顯然在同一區域網路内的定義就是->強連通分量,那麼我們将強連通分量内的點連邊為0,其他的依然保持不變,然後跑一遍最短路即可

#include <iostream>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 2e5+5;
const int N = 1e6+5;
const int inf = 0x3f3f3f3f;
struct Edge{
    int u,v,nxt,w;
}edge[N],e[N];
int head[maxn],tot;
int Head[maxn],tot2;
inline void init(){
    memset(head,-1,sizeof(head));
    memset(Head,-1,sizeof(Head));
    tot = 0;
}
inline void addedge(int u,int v,int w){
    edge[++tot] = {u,v,head[u],w};
    head[u] = tot;
}

inline void add(int u,int v,int w){
    e[++tot2] = {u,v,Head[u],w};
    Head[u] = tot2;
}
int dis[maxn];  bool tag[maxn];
struct Node{
    int u,dis;
    bool operator <(const Node &h)const{
        return dis > h.dis;
    }
};

inline int dijiastra(int s,int t,int n){
    for(int i = 0; i <= n; ++i){
        dis[i] = inf,tag[i] = false;
    }
    dis[s] = 0;
    priority_queue<Node> pq;    pq.push(Node{s,0});
    while(!pq.empty()){
        Node x = pq.top(); pq.pop();
        if(tag[x.u]) continue;
        tag[x.u] = true;
        for(int i = Head[x.u]; ~i; i = e[i].nxt){
            Edge &ee = e[i];
            if(!tag[ee.v] && dis[ee.v] > dis[x.u] + e[i].w){
                dis[ee.v] = dis[x.u] + e[i].w;
                pq.push(Node{ee.v,dis[ee.v]});
            }
        }
    }
    return dis[t];
}

bool vis[maxn];
int dfn[maxn],low[maxn],cost[maxn],dfnnum,num,color[maxn],sta[maxn],stalen;
void tarjan(int u){
    dfn[u] = low[u] = ++dfnnum;
    sta[stalen++] = u,vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        Edge &e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
    }
    if(dfn[u]==low[u]){
        ++num; int cur;
        do{
            cur = sta[--stalen];
            vis[cur] = false;
            color[cur] = num;
        }while(u!=cur);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n,m;    cin >> n >> m;
    init();
    for(int i = 0; i < m; ++i){
        int u,v,w;  cin >> u >> v >> w;
        addedge(u,v,w);
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i]) tarjan(i);
    }
    for(int i = 1; i <= n; ++i){
        for(int j = head[i]; ~j; j = edge[j].nxt){
            int u = color[i],v = color[edge[j].v];
            if(u==v) add(i,edge[j].v,0);
            else add(i,edge[j].v,edge[j].w);
        }
    }
    cout << dijiastra(1,n,n) << endl;

    return 0;
}
           

P2194 HXY燒情侶

題目描述

衆所周知,HXY已經加入了FFF團。現在她要開始喜(sang)聞(xin)樂(bing)見(kuang)地燒情侶了。這裡有n座電影院,n對情侶分别在每座電影院裡,然後電影院裡都有汽油,但是要使用它需要一定的費用。m條單向通道連接配接相鄰的兩對情侶所在電影院。然後HXY有個絕技,如果她能從一個點開始燒,最後回到這個點,那麼燒這條回路上的情侶的費用隻需要該點的汽油費即可。并且每對情侶隻需燒一遍,電影院可以重複去。然後她想花盡可能少的費用燒掉所有的情侶。問最少需要多少費用,并且當費用最少時的方案數是多少?由于方案數可能過大,是以請輸出方案數對1e9+7取模的結果。

(注:這裡HXY每次可以從任何一個點開始走回路。就是說一個回路走完了,下一個開始位置可以任選。是以說不存在燒不了所有情侶的情況,即使圖不連通,HXY自行選擇頂點進行燒情侶行動。且走過的道路可以重複走。)

這條回路上的情侶-> 強連通分量,一個強連通分量内選擇那個cost最小的點,然後方案數是每個連通分量内 cost最小的點的個數的乘積

#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int mo = 1e9+7;
const int maxn = 1e5+5;
const int inf = 0x3f3f3f3f;
int cost[maxn]; // the cost to fire
int head[maxn],tot,siz[maxn];
struct Edge{
    int u,v,nxt;
}edge[300005];
inline void addedge(int u,int v){
    edge[++tot] = {u,v,head[u]};
    head[u] = tot;
}
bool vis[maxn];
int dfn[maxn],sta[maxn],low[maxn],stalen,dfnnum,mincost[maxn],color[maxn],num;
inline void init(int n){
    for(int i = 0; i <= n; ++i){
        head[i] = -1,dfn[i] = 0,low[i] = 0,mincost[i] = inf,color[i] = 0;
    }
    num = dfnnum = stalen = tot = 0;
}
void tarjan(int u){
    dfn[u] = low[u] = ++dfnnum;
    sta[stalen++] = u; vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        Edge &e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
    }
    if(dfn[u]==low[u]){
        ++num; int cur;
        vector<int> vec;
        do{
            cur = sta[--stalen];
            mincost[num] = min(mincost[num],cost[cur]);
            vec.push_back(cost[cur]);
            vis[cur] = false;
            color[cur] = num;
        }while(u!=cur);
        for(auto i : vec){
            if(i==mincost[num])
                siz[num]++;
        }
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n;  cin >> n;
    init(n);
    for(int i = 1; i <= n; ++i) cin >> cost[i];
    int m;  cin >> m;
    for(int i = 0; i < m; ++i){
        int u,v;    cin >> u >> v;
        addedge(u,v);
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i]) tarjan(i);
    }

    long long ans = 0,ans2 = 1;
    for(int i = 1; i <= num; ++i){
            ans += mincost[i];
            ans2 = (1ll*ans2%mo*siz[i]%mo)%mo;
    }

    cout << ans << ' ' << ans2 << endl;
    return 0;
}
           

P1262 間諜網絡

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 2e5+5;
const int inf = 0x3f3f3f3f;
int bribe[maxn];
struct Edge{
    int u,v,nxt;
}edge[maxn];
int head[maxn],tot;

inline void addedge(int u,int v){
    edge[++tot] = {u,v,head[u]};
    head[u] = tot;
}
bool vis[maxn];
int dfn[maxn],low[maxn],sta[maxn],color[maxn],mincost[maxn],stalen,dfnnum,num;
int out[maxn];
inline void init(){
    memset(head,-1,sizeof(head));
    memset(mincost,0x3f,sizeof(mincost));
    memset(bribe,0x3f,sizeof(bribe));
    tot = 0;
}
void tarjan(int u){
    dfn[u] = low[u] = ++dfnnum;
    sta[stalen++] = u,vis[u] = true;
    for(int i = head[u]; ~i; i = edge[i].nxt){
        Edge &e = edge[i];
        if(!dfn[e.v]){
            tarjan(e.v);
            low[u] = min(low[u],low[e.v]);
        }else if(vis[e.v]) low[u] = min(low[u],dfn[e.v]);
    }
    if(dfn[u]==low[u]){
        ++num;  int cur;
        do{
            cur = sta[--stalen];
            vis[cur] = false;
            mincost[num] = min(mincost[num],bribe[cur]);
            color[cur] = num;
        }while(u!=cur);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    init();
    int n,p;  cin >> n >> p;
    for(int i = 0; i < p; ++i){
        int u,m;    cin >> u >> m;
        bribe[u] = m;   // stand for this people can be bribed;
    }
    int r;  cin >> r;
    for(int i = 0; i < r; ++i){
        int u,v;    cin >> u >> v;
        addedge(u,v);
    }
    for(int i = 1; i <= n; ++i){
        if(!dfn[i] && bribe[i]!=inf){
            tarjan(i);
        }
    }

    for(int i = 1; i <= n; ++i){
        if(!dfn[i]){
            cout << "NO" << endl << i << endl;
            return 0;
        }
    }

    for(int i = 1; i <= n; ++i){
        for(int j = head[i]; ~j; j = edge[j].nxt){
            int u = color[i],v = color[edge[j].v];
            if(u!=v){
                out[v]++;
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= num; ++i){
        if(!out[i]) ans += mincost[i];
    }
    cout << "YES" << endl;
    cout << ans << endl;
    return 0;
}
           

LA 4287 Proving Equivalences

命題互推(現在已經知道一些命題之間的推導關系)

問現在還需要證明多少個。

因為命題: a->b 可以抽象成有向圖,如果命題之間等價的話,就說明兩個命題之間彼此可以從 a->c 同時 c->a,這就是強連通的定義。

是以,先算出圖中的強連通分量,然後縮點,之後找出入度為0的點的個數,選擇最大的那個即是答案。