ACM新手DAY 10 并查集+拓扑排序

题解

A - X-Plosives (样题)

题目:有n个化合物,每个化合物由两种元素构成,现在按照输入顺序把所有化合物装车,当车上有k个化合物的时候,如果恰好有k种元素,就会发生危险,所以当工人拿到化合物的时候,确保它装车后不会有危险才会将它装入,问有多少个化合物被拒绝装车。

  • 并查集判环
  • 把每种元素看成结点,每个化合物看成一条边,连接两种元素,当有环存在时就会发生危险.由于这个的输入有些特别,暂且不按照并查集一般形式分解,放上完整代码。
#include <iostream>
#include <cstdio>
#include <string.h>
using namespace std;
const int MX = 1e5+10;
int parent[MX];
int findset(int x)
{
    return parent[x] == x ? parent[x] = x:findset(parent[x]);
}
int main()
{
    int a, b;
    int cnt;
    while(scanf("%d", &a) != EOF) //此处用while(1)会超时!
    {
        cnt = 0;
        for(int i = 0; i < MX; ++i) parent[i] = i;
        while(a != -1)//针对本题的情况做的输入设计,很巧妙
        {
            scanf("%d", &b);
            int roota = findset(a);
            int rootb = findset(b);//这里就是merger部分由于输入的要求产生的变动
            if(roota != rootb) parent[roota] = rootb;//没存点就存下,有存点就拒绝
            else cnt++;
            scanf("%d", &a);
        }
    printf("%d\n", cnt);
    }
}

B - 畅通工程

题目:使全省任何两个城镇间都可以实现交通(但不一定有直接的道路相连,只要互相间接通过道路可达即可)。问最少还需要建设多少条道路?

  • 并查集
  • 同样是并查集,找到自联的点就可以了。
  • 查找
int findset(int x)
{
    return parent[x] == x ? parent[x] = x:findset(parent[x]);
}
  • 合并
void merger(int le, int ri)
{
    int l = findset(le);
    int r = findset(ri);
    parent[l] = r;
}
  • 逻辑部分
for(int i=1;i<=n;i++)
            if(parent[i]==i)
                ans++;
        printf("%d\n",--ans);

C - 小希的迷宫

综合了前两道题的思路,既要保证没有环,又要保证可以都联通。主要有个坑就是,当两个输入都是0的时候输出yes.

  • 主体部分
int main()
{
    int a,b;
    while(scanf("%d%d",&a,&b))
    {
        if(a==0&&b==0)
        {
            printf("Yes\n");
            continue;
        }
        if(a==-1&&b==-1)break;//文件结束了
        for(int i=0; i<MX; i++)//初始化部分
        {
            parent[i]=i;
            vis[i]=0;
        }
        bl = 1;//初始化
        merger(a,b);//第一个读进去的也要归并
        vis[a]=1; vis[b]=1;
        while(scanf("%d%d",&a,&b)&&(a+b))//读到0就不读了
        {
            merger(a,b);
            vis[a]=1; vis[b]=1;
        }
        if(bl==0)//集合里面有
        {
            printf("No\n");
            continue;
        }
        else
        {
            bl2 = 0;
            for(int i=0; i<=MX; i++)//B题的思想,要保证都有联通的路,坑!
            {
                if(vis[i]&&parent[i]==i)
                    bl2++;
            }
            if(bl2==1) printf("Yes\n");//为什么是1不是0,原因在B题里面
            else printf("No\n");
        }
    }
    return 0;
}

D - 确定比赛名次 (样题)

题目:有N个比赛队(1<=N<=500),编号依次为1,2,3,。。。。,N进行比赛,比赛结束后,裁判委员会要将所有参赛队伍从前往后依次排名,但现在裁判委员会不能直接获得每个队的比赛成绩,只知道每场比赛的结果,即P1赢P2,用P1,P2表示,排名时P1在P2之前。现在请你编程序确定排名。

  • 拓扑排序
  • 代码
#include <iostream>
#include <cstdio>
#include <queue>
#include <vector>
using namespace std;
const int MX = 500+10;
int N, M;
vector<int> G[MX];
int indegree[MX];//记录度
priority_queue<int, vector<int>, greater<int> >q2;//从小到大
void init()//初始化
{
    for(int i = 1; i <= M; ++i)
    {
        int node1, node2;
        scanf("%d%d", &node1, &node2);
        G[node1].push_back(node2);
        indegree[node2]++; //node2的入度加一
    }
}
int topo_sort()//拓扑排序
{
    for(int i = 1; i <= N; ++i)
        if(!indegree[i]) q2.push(i);
    int cnt = 0; //用于判断有无环
    while(!q2.empty())
    {
        cnt++;
        int tmp = q2.top();//读队头元素
        q2.pop();  //弹出队头元素
        //遍历这个入度为0的节点指向的所有节点
        for(int i = 0; i < G[tmp].size(); ++i)
        {
            int node = G[tmp][i];
            indegree[node]--; //将其指向的节点的入度减1
            if(!indegree[node])
            q2.push(node);//若入度变为0,入队列
        }
        //当所有节点都经过队列弹出后说明,存在拓扑排序,即存在有向无环图
    if(!q2.empty()) cout << tmp << ' ';
    else cout << tmp; }
    if(cnt < N) return -1;//若成环则为-1
}
int main()
{
    while(scanf("%d%d", &N, &M) != EOF)
    {
        for(int i = 1; i <= N; ++i)
        {
            G[i].clear();
            indegree[i] = 0;
        } //初始化
            init();
            topo_sort();
            cout << endl;
    }
    return 0;
}

E - Legal or Not

与上一题基本差不多,修改一下输出部分就好了。

  • 修改后
void topo_sort()//注意这里的int变成了void
{
        for(int i=0; i<n; i++)
            if(indegree[i] == 0) q.push(i);

        int cnt = 0;  //记录弹出队列的节点个数
        //从队列中弹出节点进行处理
        while(!q.empty())
        {
            int c = q.front();  //读队头元素
            q.pop();            //弹出对头元素
            cnt++;
            //遍历这个入度为0的节点指向的所有节点,
            for(int i=0; i<G[c].size(); i++)
            {
                indegree[G[c][i]]--;         //将其指向的节点的入度减1
                if( indegree[G[c][i]] == 0) q.push(G[c][i]);  //若入度变为0,入队列
            }
        }
        //当所有节点都经过队列弹出后说明,存在拓扑排序,即存在有向无环图
        if(cnt == n) puts("YES");
        else puts("NO");
}
发布了47 篇原创文章 · 获赞 4 · 访问量 1312

猜你喜欢

转载自blog.csdn.net/listenhhh/article/details/97163734