图论考试题(新)

图论又考掉了

一.越狱

【问题描述】

Michael为救哥哥身陷囹圄,被关进foxriver监狱。为准备越狱,他需要散布消息给监狱中其他人来共同协作,但是监狱中鱼龙混杂,分成各个小团体,内部消息传递单向传输。问题1:初始至少需要向多少个透漏消息,使得监狱内所有人都获知消息。

问题2,至少需要添加几条传输线路(边),使任意向一个人散步消息后,经过若干次传送,监狱内所有的人最终都能得到消息。

【输入格式】

从文件break.in中输入数据。

输入的第一行包含一个整数 N:监狱人数(2 <= N <= 100)。用前 N 个正整数标识每个人。接下来 N 行中每行都表示一个消息传递列表(分发列表)。第 i+1 行包括 i 的接收消息的人的标识符。每个列表用 0 结束。空列表只用一个 0 表示。

【输出格式】

输出到文件break.out中。

输出的第一行包含一个正整数:问题1的解。第二行应该包含问题2的解。

【样例输入】

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

【样例输出】

1
2
先用tarjan缩点,再进行讨论。第一问明显是求现在图入度为0的个数
主要是第二问,其实就是要构建一个环,让每个点都在环中,也就是出度入度都要大于0
我们可以考虑将每一个出度为0的点向每个入度为0的点对应地连一条边。
但是如果有一方的点要大一些,那么就要多加它们的差值的边,其实也就是求最大值
特判:如果只有一个点,不需要加边

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int MAXN = 103;
int n;
vectorG1[MAXN];
vectorG[MAXN];
int dfn[MAXN] , low[MAXN];
bool vis[MAXN];
int belong[MAXN];
stacksta;
int inde;
int cnt;
void tarjan( int x ){
    sta.push( x );
    vis[x] = 1;
    dfn[x] = low[x] = ++inde;
    for( int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i];
        if( !dfn[v] ){
            tarjan( v );
            low[x] = min( low[x] , low[v] );
        }
        else if( vis[v] )
            low[x] = min( low[x] , dfn[v] );
    }
    if( low[x] == dfn[x] ){
        int k = sta.top();
        sta.pop();
        cnt ++;
        while( k != x ){
            belong[k] = cnt;
            vis[k] = 0;
            k = sta.top();
            sta.pop();
        }
        belong[x] = cnt;
        vis[x] = 0;
    }
}
int ans;
void dfs( int x ){
    vis[x] = 1;
    for( int i = 0 ; i < G1[x].size() ; i ++ ){
        int v = G1[x][i];
        if( vis[v] ) continue;
        dfs( v );
    }
}
int rd[MAXN] , cd[MAXN];
bool flag[MAXN][MAXN];
int main()
{
    //freopen( "break.in" , "r" , stdin );
    //freopen( "break.out" , "w" , stdout );
    scanf( "%d" , &n );
    for( int i = 1 ; i <= n ; i ++ ){
        int x;
        scanf( "%d" , &x );
        while( x ){
            G[i].push_back( x );
            scanf( "%d" , &x );
        }
    }
    for( int i = 1 ; i <= n ; i ++ ){
        if( !dfn[i] )
            tarjan( i );
    }
    memset( vis,  0 , sizeof( vis ) );
    ans = 0;
    int sum = 0;
    for( int i = 1 ; i <= n ; i ++ )
        for( int j = 0 ; j < G[i].size() ; j ++ ){
            int v = G[i][j];
            if( belong[v] != belong[i] && !flag[belong[v]][belong[i]]){
                G1[belong[i]].push_back( belong[v] );
                cd[belong[i]] ++ , rd[belong[v]] ++;
                flag[belong[v]][belong[i]] = 1;
            }
        }
    for( int i = 1 ; i <= cnt ; i ++ ){
        if( rd[i] == 0 ) sum ++ ;
        if( cd[i] == 0 ) ans ++;
    }
    printf( "%d\n" , sum );
    if( cnt == 1 )
        ans = sum = 0;
    printf( "%d" , max( sum , ans ) );
    return 0;
}

二.生产

【问题描述】
工厂为了生产一种复杂的产品,给各个生产部门制定了详细的生产计划。那么,就经常会有生产部门要把产品送到另一个生产部门作为原料。这是一个注重产品质量的工厂,所以每当有产品要从A部门运到B部门时,都要先从A部门送到质量检验处,检验合格后再从质量检验处运到B部门。
有些部门之间有传送带连接,厂长想知道每次将产品从一个部门运送到另一个部门最少需要多长时间。
【输入格式】
第一行两个整数n、m,n表示部门数量,m表示传送带数量。出于方便,1号部门是质量检验处。
接下来m行,每行三个整数u、v、w,表示有一条从u部门到v部门的传送带,传送过去需要w个单位时间。注意传送带是单向的。
接下来一个整数q,表示有q次运送。
接下来q行,每行两个数a、b,表示这一次要将产品从a部门运送到b部门。
【输出格式】
输出q行,每行一个整数,表示这次运送最少需要的时间。若没有传送方案,输出-1。
【样例输入】
5 5
1 2 3
1 3 5
4 1 7
5 4 1
5 3 1
3
4 2
5 3
2 3
【样例输出】
10
13
-1
【数据规模与约定】
30%的数据,n≤100,m≤500,w=1
60%的数据,n≤100,m≤5000
另20%的数据,q=1
100%的数据,2≤n≤3000,m≤100000,2≤a,b≤n,
q≤100000,1≤u,v≤n,1≤w≤10000
有些部门之间可能有多条传送带。
工厂的员工都非常尽职尽责,他们的认真和热情决定了产品的完美,所以不必考虑产品不合格的情况。

直接从1开始跑两遍dijkstra,其中一次反着跑,这样就可以求中转
其实dijkstra跑重边是没有问题的

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define ll long long
const int MAXN = 3003;
int n , m;
bool vis[MAXN];
int dis1[MAXN] , dis2[MAXN];
struct node{
    int v , w;
    node(){}
    node( int V , int W ){
        v = V;
        w = W;
    }
    friend bool operator < ( node a , node b ){
        return a.w > b.w;
    }
};
vectorG1[MAXN];
vectorG[MAXN];
int cnt;
int X[100003] , Y[100003] , Z[100003];
void dijk( ){
    priority_queueq;
    memset( dis2 , 0x7f , sizeof( dis2 ) );
    memset( dis1 , 0x7f , sizeof( dis1 ) );
    dis1[1] = dis2[1] = 0;
    q.push( node( 1 , 0 ) );
    while( !q.empty() ){
        node tx = q.top();
        q.pop();
        //if( vis[tx.v] ) continue;
        //vis[tx.v] = 1;
        for( int i = 0 ; i < G1[tx.v].size() ; i ++ ){
            int v = G1[tx.v][i].v , w = G1[tx.v][i].w;
            if( dis1[v] > dis1[tx.v] + w ){
                dis1[v] = dis1[tx.v] + w;
                q.push( node( v , dis1[v] ) );
            }
        }
    }
    while( !q.empty() ) q.pop();
    memset( vis , 0 , sizeof( vis ) );
    q.push( node( 1 , 0 ) );
    while( !q.empty() ){
        node tx = q.top();
        q.pop();
        for( int i = 0 ; i < G[tx.v].size() ; i ++ ){
            int v = G[tx.v][i].v , w = G[tx.v][i].w;
            if( dis2[v] > dis2[tx.v] + w ){
                dis2[v] = dis2[tx.v] + w;
                q.push( node( v , dis2[v] ) );
            }
        }
    }
}
int main()
{
    //freopen( "production.in" , "r" , stdin );
    //freopen( "production.out" , "w" , stdout );
    scanf( "%d%d" , &n , &m );
    for( int i = 1 ; i <= m ; i ++ ){
        scanf( "%d%d%d" , &X[i] , &Y[i] , &Z[i] );
		G[X[i]].push_back( node( Y[i] , Z[i] ) );
        G1[Y[i]].push_back( node( X[i] , Z[i] ) );
    }
    dijk();
    int q;
    scanf( "%d" , &q );
    for( int i = 1 ; i <= q ; i ++ ){
        int x , y;
        scanf( "%d%d" , &x , &y );
        if( dis1[x] == 0x7f7f7f7f || dis2[y] == 0x7f7f7f7f ) {
            printf( "-1\n" );
        }
        else
            printf( "%d\n" , dis1[x] + dis2[y] );
    }
    return 0;
}

三.大逃亡

问题描述】
大COS所在的FC幽寂即将与皇家钨冀进行一场足球友谊赛。与一般的足球比赛11人制不同,这场友谊赛两队各有n位球员同时在场上奔跑,场面十分壮(hun)观(luan)。当然,球还是只有一个。
现在,FC幽寂主教练卡犇要制定战术。在进攻上,为了保证团队的协调有序,(直接)传球必须在特定球员之间进行,方向不限。例如门将只会与最近的几个后卫传球。
受到个人能力和球员间默契的影响,可直接传球的两个球员之间有一个特定的失误系数,越大则表示越容易失误。不可直接传球的两个球员之间想要进行球的传递,则需通过其他球员来间接传球(战术已保证一个球员能直接或间接传给其他任意球员)。可直接传球球员之间也可以间接传球。根据木桶原理,间接传球的失误系数等于传球路线中所有直接传球的最大的失误系数。
在制定高级战术过程中,卡犇需要知道两名球员间传球(包括直接和间接)可能达到的最小的失误系数是多少。然而由于球员太多……

【输入格式】
输入文件名为friendly.in。
第一行两个正整数n、m,分别表示FC幽寂队场上球员数和可直接传球的球员有多少对。
接下来n行,每行一个字符串,分别表示每位球员姓名。
接下来m行,每行两个字符串s1、s2和一个正整数k,之间用一空格隔开,表示名为s1和名为s2的两个球员可以直接传球,失误系数为k。
第n+m+2行一个正整数q,表示卡犇的询问数。
接下来q行,每行两个字符串s1、s2,表示卡犇想知道名为s1和名为s2的两个球员之间传球可达的最小失误系数。
数据没有多余的空格和回车。
【输出格式】
输出文件名为friendly.out。
共q行,每行一个正整数,依次表示每个询问的答案。

有点难想,先跑一个最小生成树,再用LCA倍增即可
可用map存名字

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define ll long long
const int MAXN = 100003;
map mp;
int n , m;
bool vis[MAXN];
struct edge{
	int u , v , w;
	friend bool operator < ( edge a , edge b ){
        return a.w < b.w;
    }
}a[MAXN];
struct node{
    int v , w;
    node(){}
    node( int V , int W ){
        v = V;
        w = W;
    }

};
vectorG[MAXN];
int fa[MAXN];
int height[MAXN];
int f[MAXN][23];
int zh[MAXN][23];
int findSet( int x ){
    if( x != fa[x] ) fa[x] = findSet( fa[x] );
    return fa[x];
}
void dfs( int x , int fax ){
    for( int i = 0 ; i < G[x].size() ; i ++ ){
        int v = G[x][i].v , w = G[x][i].w;
        if( v == fax ) continue;
        height[v] = height[x] + 1;
        dfs( v , x );
        f[v][0] = x;
        zh[v][0] = w;
    }
}
void jump( int &x , int mu , int &ans ){
    for( int i = 20; i >= 0 ; i -- ){
        if( height[f[x][i]] >= mu ){
            ans = max( ans , zh[x][i] );
            x = f[x][i];
        }
    }
}
int LCA( int x , int y ){
    int ans = 0;
    if( height[x] > height[y] )
        jump( x , height[y] , ans );
    else if( height[y] > height[x] )
        jump( y , height[x] , ans );
    if( x == y ) return ans;
    for( int i = 20 ; i >= 0 ; i -- ){
        if( f[x][i] != f[y][i] ){
            ans = max( zh[x][i] , ans );
            ans = max( zh[y][i] , ans );
            x = f[x][i] , y = f[y][i];
        }
    }
    ans = max( ans , max( zh[x][0] , zh[y][0] ) );
    return ans;
}
int main()
{
    //freopen( "friendly.in" , "r" , stdin );
    //freopen( "friendly.out" , "w" , stdout );
    scanf( "%d%d" , &n , &m );
    int cnt = 0;
    for( int i = 1 ; i <= n ; i ++ ){
        string x;
        cin >> x;
        mp[x] = ++cnt;
        fa[i] = i;
    }
    for( int i = 1 ; i <= m ; i ++ ){
        string x , y;
        cin>>x>>y;
        int z;
        scanf( "%d" , &z );
        a[i].u = mp[x] , a[i].v = mp[y] , a[i].w = z;
    }
    sort( a + 1 , a + m + 1 );
    int k = 0 ;
    for( int i = 1 ; i <= m ; i ++ ){
        int u = findSet( a[i].u ) , v = findSet( a[i].v );
        if( u != v ){
            k ++;
            G[u].push_back( node( v , a[i].w) );
            G[v].push_back( node( u , a[i].w) );
            fa[u] = v;
        }
        if( k == n - 1 )
            break;
    }
    height[1] = 1;
    dfs( 1 , 0 );
    for( int j = 1 ; j <= 20 ; j ++ ){
        for( int i = 1 ; i <= n ; i ++ ){
            f[i][j] = f[f[i][j-1]][j-1];
            zh[i][j] = max( zh[i][j-1] , zh[f[i][j-1]][j-1] );
        }
    }
    int q;
    scanf( "%d" , &q );
    for( int i = 1 ; i <= q ; i ++ ){
        string x , y;
        cin >> x >> y;
        printf( "%d\n" , LCA( mp[x] , mp[y] ) );
    }
    return 0;
}

你可能感兴趣的:(图论,总结)