王道机试指南--第五章(图论)

文章目录

          • 并查集
            • Head of a Gang
          • 最短路径(技巧)

注意:当用邻接矩阵来存图的时候,需要注意输入样例中的两个点可能有多个路径,需要使其赋值为最短的那一条。

并查集
Head of a Gang

题目链接Head of a Gang
题目大意:给出一些人的关系,有权重,从中选出集合元素个数大于等于3人且总权重大于k的团队,并且输出团队的队长(与其相连的权重最大)。

参考博客九度oj-head of gang-并查集
思路:本题是传统并查集的变形。大致思路是,先建立二级并查集,最后处理并查集,得出符合条件的团队。
结构体采用名字和权重,建立结构体数组per。建立并查集的过程基本不变,只不过,要计算团队的人数sum,通过合并的时候,加入根节点处的sum。father下标是在输入中出现的顺序,father用于后面得到根节点,建立并查集的过程中,更新per数组。最后根据条件层层筛选出符合条件的团队。
代码如下:

#include 
#include 
#include 
#include 
using namespace std;

const int MAX = 2000;
struct person
{
    char name[3];//名字
    int time;//与其相关的权值之和
};
struct header
{
    char name[3];//名字
    int members;//团队总人数
    bool operator < (const header &b) const//按名字升序排序
    {
        int tmp = strcmp(name, b.name);
        return tmp<0;
    }
};
person per[MAX];//人
header head[MAX/2];//团队
int father[MAX];
int sum[MAX];//记录每个集合的个数

int findroot(int x)
{
    if(father[x]==x) return x;
    int r, t = x;
    while(father[t]!=t) t = father[t];
    r = t;
    while(father[x]!=x)
    {
        t = father[x];
        father[x] = r;
        x = t;
    }
    return r;
}
void join(int a, int b)
{
    int fa = findroot(a), fb = findroot(b);
    if(fa != fb)
    {
        father[fa] = fb;
        sum[fb] += sum[fa];
    }
}

int main()
{
    int N, K, i, j;
    while(scanf("%d%d", &N, &K)!=EOF)
    {
        char name1[5];
        char name2[5];
        char name[15];
        int t, num = 0;//num-1为per数组的大小

        for(int i=0; i<MAX; i++)
        {
            father[i] = i;
            sum[i] = 1;
        }
        while (getchar() != '\n');
        while(N--)
        {
            int i = 0, j = 0;
            scanf("%s%s%d", name1, name2, &t);
            for(i=0; i<num; i++)
            {
                if(strcmp(name1, per[i].name)==0) break;
            }
            if(i==num)//数组per中没有
            {
                strcpy(per[i].name, name1);
                per[i].time = t;
                num++;
            }
            else
            {
                per[i].time += t;//累加权值
            }
         
            for(j=0; j<num; j++)
            {
                if(strcmp(name2, per[j].name)==0) break;
            }
            if(j==num)//数组per中没有
            {
                strcpy(per[j].name, name2);
                per[j].time = t;
                num++;
            }
            else
            {
                per[j].time += t;//累加权值
            }
            join(i, j);
        }
		//这一步很重要,将同一个团伙的成员的根节点置成一样的,方便后来的查询。
        for(int i=0; i<num; i++) findroot(i);
        //处理并查集
        int index = 0;
        int ans = 0;//符合条件的团队个数
        for(i=0; i<num; i++)
        {
            int maxt = 0;//一个团队中的最大权值
            int maxind = 0;//一个团队中的头目下标
            int sum_talk = 0;//一个团队的总权值
            if(father[i]==i && sum[i]>2)//总人数大于2的团队
            {
                for(j=0; j<num; j++)
                {
                    if(father[j]==i)//该团队成员
                    {
                        if(per[j].time>maxt)//寻找团队头目
                        {
                            maxt = per[j].time;
                            maxind = j;
                        }
                        sum_talk += per[j].time;
                    }
                }
                if(sum_talk/2>K)//二倍
                {
                    strcpy(head[index].name, per[maxind].name);
                    head[index].members = sum[i];
                    index++;
                    ans++;
                }
            }
        }
        sort(head, head+index);
        printf("%d\n", ans);
        if(ans!=0)
        {
            for(int i=0; i<index; i++)
            {
                printf("%s %d\n", head[i].name, head[i].members);
            }
        }
    }
    return 0;
}
最短路径(技巧)

题目链接最短路径
参考博客九度 OJ1100 最短路径(需要使用高精度整数)

  • 思路:求最短路径,但是边的权值可能超出范围,如果单纯使用最短路算法,需要解决大整数问题。但是这个题目特殊,边是以2倍的关系递增,也即当输入两个点时,如果两个点已经在一个连通图中,那么它们之间的距离一定小于当前边( 2 0 + 2 1 + . . . + 2 k − 1 < 2 k 2^0+2^1+...+2^{k-1}<2^k 20+21+...+2k1<2k),那么就任何最短路径中均不需要该边。如果两个点不在一个连通图中,那么通过该边可以使得两个连通图连通,并且一个连通图中的点到另一个连通图中的点的最短路径一定经过该边(因为之后的边一定大于该边),所以可以通过改变获得一系列的最短路径。
    • ①如果a和b属于同一个集合那么当前边就直接舍弃掉,因为之前的所有边之和都不会大于该边( 2 0 + 2 1 + . . . + 2 k − 1 < 2 k 2^0+2^1+...+2^{k-1}<2^k 20+21+...+2k1<2k),而且此时图又是连通的,故舍弃该边。
    • ②如果a和b不属于同一个集合,找到a集合中的所有点和b集合中的所有点,通过a-b的边将各自的集合中的点连接起来,随后将两个集合合并。

代码:

#include 
#include 
using namespace std;

const int MAX = 101;
int father[MAX];
int d[MAX][MAX];
int N, M, a, b;
int findRoot(int x)
{
    if(father[x]==-1) return x;
    int root, t = x;
    while(father[t]!=-1) t = father[t];
    root = t;
    while(father[x]!=-1)
    {
        t = father[x];
        father[x] = root;
        x = t;
    }
    return root;
}
void join(int a, int b)
{
    int fa = findRoot(a), fb = findRoot(b);
    if(fa!=fb) father[fa] = fb;
}
int main()
{
    while(cin >> N >> M)
    {
        int dis = 1;
        memset(father, -1, sizeof(father));
        memset(d, 0, sizeof(d));
        for(int i=0; i<M; i++)//类似于MST的求法,边的权值不断增加
        {
            cin >> a >> b;
            int fa = findRoot(a), fb = findRoot(b);
            if(fa==fb)//同一个集合中,a到b的最短路径已求出
            {
                dis = (2*dis)%100000;
                continue;
            }
            for(int k=0; k<N; k++)//求a和b集合中所有点的最短路径(经过a-b边)
            {
                if(fa!=findRoot(k)) continue;
                for(int j=0; j<N; j++)
                {
                    if(fb!=findRoot(j)) continue;
                    d[k][j] = d[j][k] = (d[k][a]+dis+d[b][j])%100000;//最短路径长度
                }
            }
            dis = (2*dis)%100000;
            join(a, b);
        }
        for(int i=1; i<N; i++)//输出路径长度
        {
            if(d[0][i]==0) cout << -1 << endl;
            else           cout << d[0][i] << endl;
        }
    }
    return 0;
}

你可能感兴趣的:(王道计算机考研——机试指南)