【洛谷 P1137】旅行计划——spfa拓展

题目描述

小明要去一个国家旅游。这个国家有N个城市,编号为1~N,并且有M条道路连接着,小明准备从其中一个城市出发,并只往东走到城市i停止。

所以他就需要选择最先到达的城市,并制定一条路线以城市i为终点,使得线路上除了第一个城市,每个城市都在路线前一个城市东面,并且满足这个前提下还希望游览的城市尽量多。

现在,你只知道每一条道路所连接的两个城市的相对位置关系,但并不知道所有城市具体的位置。现在对于所有的i,都需要你为小明制定一条路线,并求出以城市i为终点最多能够游览多少个城市。

输入格式:

输入的第1行为两个正整数N, M。

接下来M行,每行两个正整数x, y,表示了有一条连接城市x与城市y的道路,保证了城市x在城市y西面。

输出格式:

输出包括N行,第i行包含一个正整数,表示以第i个城市为终点最多能游览多少个城市。

数据范围:

对于100%的数据,N ≤ 100000,M ≤ 200000

这道题第一反应dp,一个点的答案就是所有指向它的点中最大的+1,。可是正向dp不是很方便,因为初始点不止一个。

然后我就接触了一个黑科技——记忆化搜索。

本质还是搜索,不过是带动归思想的搜索,剪掉的枝多到不知道那里去了…

搜索过程中先判断该状态有没有记录,如果有就直接返回,如果没有就往下搜索并记录。

效率和dp一样?

代码如下;

#include<cstdio>
#include<iostream>
using namespace std;
struct edge{
    int to,next;
}ed[400001];
int head[100001]={0};
int n,m,size=0;
int dp[100001]={0};
void read(int &x)
{
    char c=getchar();x=0;
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
}
void add(int from,int to)
{
    size++;
    ed[size].to=to;
    ed[size].next=head[from];
    head[from]=size; 
}
int dfs(int u)
{
    if(dp[u]) return dp[u];
    dp[u]=1;
    for(int i=head[u];i;i=ed[i].next)
    {
        int v=ed[i].to;
        dp[u]=max(dp[u],dfs(v)+1);
    }
    return dp[u];
}
int main()
{
    read(n),read(m);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        read(u),read(v);
        add(v,u);
    }
    for(int i=1;i<=n;i++)
    {
        printf("%d\n",dfs(i));
    }
    return 0;
}

另外说一下spfa的方法。

spfa大概是最常用的最短路算法了,重要的不是它的代码,而是它的思想,很多不是求最短路的里的题也可以用“hortest path faster algorithm”/滑稽

spfa是怎么实现的?

每次取出队首元素u,尝试对其指向的点v进行松弛操作,如果更新成功了,并且v不在队列中,就把v入队,不断重复直到队列为空。

好像有点像bfs…

bfs是针对无权图求最短路的好方法,spfa却可以用于有权图。

这两个的作用远不止最短路…

只要可以用前一个点更新后一个点的题,好像它们都能得分…

这个题显然就是这样的辣!(我觉得和dp没什么区别)

因为是无权图,所以可以用也bfs(其实原理一样)。

如果已知前一个点的f[u],那么就能推出后一个点f[v]=f[u]+1;

所以我们先找出初始点入队就可以了。

可是需要稍微改动一下。

因为一个点可能通过多个点到达,而这几个点的数值可能不一样。怎么办?

很显然,我们可以只用最大的点来更新。

由于bfs是通过队列实现的,所以点是按照数值从小到大的顺序处理的,所以越大的点处理地越晚。

那我们就可以只用能到达这个点中最晚被处理的点更新它就好啦。这样保证每个点被处理一次。

#include<cstdio>
#include<iostream>
#include<queue>
using namespace std;
struct edge{
    int next,to;
}ed[400001];
int head[100001]={0};
int f[100001]={0};
bool vis[100001]={0};
int pre[100001]={0};
int n,m,size=0;
void read(int &x)
{
    char c=getchar();x=0;
    while(c<'0'||c>'9') c=getchar();
    while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
}
void add(int from,int to)
{
    size++;
    ed[size].to=to;
    ed[size].next=head[from];
    head[from]=size;
}
void spfa()
{
    queue <int> q;
    for(int i=1;i<=n;i++)
    {
        if(!pre[i])
        {
            q.push(i);
            vis[i]=1;
            f[i]=1;
        }
    }
    while(!q.empty())
    {
        int u=q.front();
        q.pop();
        for(int i=head[u];i;i=ed[i].next)
        {
            int v=ed[i].to;
            f[v]=f[u]+1;
            pre[v]--;
            if(!vis[v]&&!pre[v])                 //如果再没有点能到v,说明u是最后一个能更新v的点
            {
                q.push(v);
                vis[v]=1;
            }
        }
    }
}
int main()
{
    read(n),read(m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        read(x),read(y);
        add(x,y);
        pre[y]++;                               //记录能到达y点的点的数量
    }
    spfa();
    for(int i=1;i<=n;i++)
    {
        printf("%d\n",f[i]);
    }
    return 0;
}

用spfa解非最短路问题还有noip 2009的最优贸易,那道题有环,可是没关系!转几圈不就是了。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值