HDU-4857逃生(反向拓扑排序)(重点是思想)


意:有N个人,M个优先级a,b表示a优先于b,并且每个人有个编号的优先级,输出顺序。


这道题一看也许你就知道是拓扑排序,但是这个和普通 的拓扑有些不一样!!


先来看看拓扑的讲解(做此题的时候我还不会拓扑)、

拓扑排序的原理及其实现


刚开始我就是用普通的拓扑排序+优先队列

让队列中最小的先出队~

当时我觉得没有什么不对!

附上代码:

#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=100000+10;
const int MAXN=30000+10;
int u[maxn],v[maxn];
int input[MAXN],list[MAXN];
int first[MAXN],NEXT[maxn],vis[MAXN];
bool map0[MAXN][MAXN];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        memset(input,0,sizeof(input));
        memset(first,-1,sizeof(first));
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d",&u[i],&v[i]);
            input[v[i]]++;
            NEXT[i]=first[u[i]];
            first[u[i]]=i;
        }
//        for(int i=1;i<=n;i++)
//        {
//            printf("%d ",input[i]);
//        }
//        printf("\n");
        priority_queue< int,vector<int>,greater<int> >q;
        for(int i=1; i<=n; i++) //先把入度为1先扔进去
        {
            if(input[i]==0)
            {
                q.push(i);
            }
        }
        int step=0;
        while(!q.empty())
        {
            int top=q.top();
            q.pop();
            int k=first[top];
            while(k!=-1)
            {
                input[v[k]]--;
                if(input[v[k]]==0)
                {
//                    printf("%d*-*\n",v[k]);
                    q.push(v[k]);
                }
                k=NEXT[k];
            }
            list[++step]=top;
        }
        for(int i=1; i<=step; i++)
        {
            printf("%d ",list[i]);
        }
        printf("\n");
    }
}

天真的以为我一直先输出的是队列中最小的数,这么排出顺序一定是小的在钱,大的比较靠后


然而有一组数据

1
10 9
6 4
4 1
3 9
9 2
5 7
7 8
1 10
2 10
8 10

上面代码输出是  :3 5 6 4 1 7 8 9 2 10

但是发现1不是最靠前的

题意是如果有多种情况,必须先考虑1先走,然后才考虑2走,然后考虑3

就是如果没说1在2后面(直接或者间接的关系表面1在2后面),那么1就要在2前面

那么这样你就会发现这个输出是错误的!!

正确的应该是 6 4 1 3 9 2 5 7 8 10

这样1在2前面而题意说3在2前面,这组输出就是我们要的答案

然而如何得到这组答案这是思维的重点

首先看看为什么会出现这种错误??

其实根源就是优先队列的毛病

出队的时机不对,如果把握出队的时机?

我们要最小的在前面(最小的先出)然而在队列中最小的是时刻在变!!

比如1还没有进入队列的时候2是最小的,当1进入队列后1才是最小的!!

这样导致一个结果是,如果2先进队列了,那么2的最小的,先出去了!!然后1进来了

那么根本无法保证1和2的出队顺序是一直是1在2前面

然而我们换个思路,这个问题是出队出的早了!!

那么我们让出队出晚点,让大的数先出队!!!

相对的,小的数就会留在队列中!!(越小越留到最后)

这样1和2肯定能同时留在队列中,而1肯定要比2后出

这样保证了1和2的绝对出栈顺序,然后反过来输出就OK了!!

#include<stdio.h>
#include<queue>
#include<string.h>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn=100000+10;
const int MAXN=30000+10;
int u[maxn],v[maxn];
int input[MAXN],list[MAXN];
int first[MAXN],NEXT[maxn],vis[MAXN];
bool map0[MAXN][MAXN];
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        memset(input,0,sizeof(input));
        memset(first,-1,sizeof(first));
        memset(vis,0,sizeof(vis));
        for(int i=1; i<=m; i++)
        {
            scanf("%d%d",&u[i],&v[i]);
            input[u[i]]++;
            NEXT[i]=first[v[i]];
            first[v[i]]=i;
        }
//        for(int i=1;i<=n;i++)
//        {
//            printf("%d ",input[i]);
//        }
//        printf("\n");
        priority_queue<int>q;
        for(int i=1; i<=n; i++) //先把出度为0先扔进去
        {
            if(input[i]==0)
            {
                q.push(i);
            }
        }
        int step=0;
        while(!q.empty())
        {
            int top=q.top();
            q.pop();
            int k=first[top];
            while(k!=-1)
            {
                input[u[k]]--;
                if(input[u[k]]==0)
                {
//                    printf("%d*-*\n",v[k]);
                    q.push(u[k]);
                }
                k=NEXT[k];
            }
            list[++step]=top;
        }
        for(int i=step; i>=2; i--)
        {
            printf("%d ",list[i]);
        }
        printf("%d\n",list[1]);
    }
}








评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值