旅行 关于SPFA的做法

旅行
Description
悠悠岁月,不知不觉,距那传说中的pppfish晋级泡泡帝已是过去数十年。数十年中,这颗泡泡树上,也是再度变得精彩,各种泡泡天才辈出,惊艳世人,然而,似乎不论后人如何的出彩,在他们的头顶之上,依然是有着一道身影而立。
泡泡帝,pppfish。现在,pppfish即将带着被自己收服的无数个泡泡怪前往下一个空间,而在前往下一个空间的道路上,有N个中转站,和M条空间虫洞连接中转站(双向通道,可有重边,可有环),然而,通过虫洞是要一定的条件的,pppfish将手下所有泡泡怪编号为1,2 … +∞,对于每个空间虫洞,有两个值L和R,表示此虫洞只允许编号从L到R的泡泡怪通过。
pppfish现在在1号中转站,他想带尽可能多的泡泡怪到达N号中转站,于是pppfish找到了机智的你,希望你告诉他最多可以带多少个泡泡怪,同时他还想知道所有泡泡怪的编号(若有多组解取字典序最小的一组 )
Input
第一行两个用空格隔开的整数N,M接下来M行,每行四个用空格隔开的整数a,b,l,r 表示在a,b中转站间有一个空间虫洞允许编号l~r的泡泡怪通过。
Output
第一行一个整数ans,表示最多能携带的泡泡怪数量。接下来一行ans个用空格隔开的正整数,表示泡泡怪的编号,从小到大依次输出,如果没有泡泡怪能通过只要输出“0”就可以了。
Sample Input
Input1:
4 4
1 2 1 10
2 4 3 5
1 3 1 5
2 4 2 7
Input2:
2 2
1 2 1 3
1 2 4 6
Sample Output
Output1:
6
2 3 4 5 6 7
Output2:
3
1 2 3
Data Constraint
30%的数据 1 <= N,M <= 10
100%的数据 2 <= N <= 1000, 0 <= M <= 3000, 1 <= a, b <= N, 1 <= l <= r <= 10^6

首先,题目大意为泡泡帝通过时空虫洞,单向地带着泡泡怪从1号中转站到达n号中转站(因为如果泡泡帝往回走,就相当于没有走这条路),并且,因为是单向的路径,每条通道都只能够通过[l,r]编号的泡泡怪,所以,很容易知道,最后得到的泡泡怪必然是该路径上所有通道的[l,r]的交集。
那么,我们就可以肯定,最后得到的泡泡怪区间[l,r],l必然是其中一条路径的允许通过区间的左端点,r也同理是某个区间的右端点。
同时,我们可以发现M(通道数量)是在3000以内的,所以我们可以大胆的直接枚举所有边的左/右端点。不过,我们如果同时枚举左右端点,那样一来复杂度高,二来不好操作,所以我们可以选择枚举其中一边作为本次搜索的端点。
那么,我们就可以通过SPFA,搜索1~n号中转站,来得到另一边端点的最大/最小值,然后拿本次得到的答案(也就是此时枚举端点所得到的区间大小)与之前的答案比较,取其中更优的值(区间更大的值或者区间相同情况下,更加偏向左边也就是字典序更小的值),最后得到的答案就是我们要求的最优区间了。
具体的SPFA搜索过程,则是从1号中转站开始,使用队列的方式,将即将搜索的点(也就是存在边与此时搜索的点相连)加入队列。搜索从队头开始,搜索完该点,就弹出队首。而搜索的过程中,先将搜索的通道的区间与被枚举的端点对比,判断此时正在被枚举的区间端点能否通过该通道,若不能,则跳过该通道;若能通过通道,则将即将被搜索的点此时可得到的区间另一端的值与此时正在搜索的点、通道的区间另一端的值进行对比,判断是否可以进行更新其区间另一端的值,若可以更新,则继续判断此点在不在队列中(若不在队列,则加入队列)。
最后,在每次SPFA搜索结束后,只需要判断一下此时枚举的端点与n号中转站的区间另一端的值和之前得到的答案,根据其区间长度与字典序判断是否更新答案,就可以解决问题了。
当然,如果希望算法的更快,也可以在SPFA中剪枝。

代码如下(此代码选择枚举通道左端点):

#include <bits/stdc++.h>

using namespace std;

struct node{
    int to,next,left,right;
}v[6010];
int n,m,tot,tail,head,ansl,ansr;
int q[6010],h[6010];
bool bo[6010];
int ri[3010];

void add(int ,int ,int ,int );
void find(int );

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int a,b,l,r;
        scanf("%d%d%d%d",&a,&b,&l,&r);
        add(a,b,l,r);
        add(b,a,l,r);
    }
    ansr=-1; 
    for (int i=1;i<=tot;i+=2)
    {
        head=1;
        tail=1;
        q[tail]=1;
        bo[1]=true;
        find(v[i].left);
    }
    if (ansr==-1)
    {
        printf("0");
        return 0;
    }
    printf("%d\n",ansr-ansl+1);
    for (int i=ansl;i<ansr;i++)
        printf("%d ",i);
    printf("%d",ansr);
    return 0;
}

void find(int l)
{
    memset(ri,0,sizeof(ri));
    ri[1]=(1<<20); 
    while (head<=tail)
    {
        int root=q[head];
        bo[q[head]]=false;
        head++;
        for (int jump=h[root];jump;jump=v[jump].next)
            if ((v[jump].left<=l) && (v[jump].right>=l) && (ri[v[jump].to]<min(v[jump].right,ri[root])))
            {
                ri[v[jump].to]=min(v[jump].right,ri[root]);
                if (!bo[v[jump].to])
                {
                    q[++tail]=v[jump].to;
                    bo[v[jump].to]=true;
                }
            }
    }
    if ((ri[n]-l>ansr-ansl)||(ri[n]-l==ansr-ansl&&l<ansl))
    {
        ansr=ri[n];
        ansl=l;
    }
    return ;
}

void add(int f,int t,int l,int r)
{
    v[++tot].to=t;
    v[tot].next=h[f];
    v[tot].left=l;
    v[tot].right=r;
    h[f]=tot;
    return ;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值