[luogu1710]地铁涨价(bfs)

72 篇文章 0 订阅

题目描述

传送门

题解

  • 转化问题

这里路线涨价明显等同于删边,所以我们可以把问题倒过来思考:
图上依次(倒序)加边,问每个点成为最终图最短路的时间

  • 分析

原图的点1到达点i的最短路为dis[i],当前状态下点1到达点i最短路为d[i]。下面称d[i]==dis[i]的点i为扩展点。
通过分析最短路性质发现,某个点v新成为扩展点情况有两个
加边(u,v)更新,且dis[u]==d[u]&&dis[v]==d[u]+1&&d[v]!=dis[v]
邻居u突然成为最终图最短路,且dis[v]==d[u]+1&&d[v]!=dis[v]
(其实上面是同一种情况XD)
重要的是,每个点只会被更新1次,体现在了上面的强调处。这是很显然的,因为这题答案是唯一确定的,但这个是降低复杂度的重要条件。

  • 确定算法

首先把最终图的最短路情况dis[i]求出来。然后重建图,去掉所有待加边。
然后依次加入待加边,检测边的两端是否符合条件1,若符合,则进行深度优先搜索,对新成为扩展点邻居进行条件2判断。
将每次新成为扩展点的数目记录,最后处理出答案。

以上内容转自luogu月赛题解

可以发现,由于保证了每一个点只被访问一遍,整个算法的复杂度是 O(n) 的。而非常关键的一点在于当前最短路与实际最短路的比较。也就是说,当一个点的当前最短路等于实际最短路了之后,它就相当于是被固定了,不用再被访问了。打比赛的时候一直在想每一次都要判断是否是实际最短路,其实这是完全没有必要的。这种思想以后要注意。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
#define N 200005

int n,m,Q,cnt;
int tot,point[N],nxt[N*2],v[N*2];
int del[N],dis[N],d[N],ans[N];
bool vis[N];
struct hp{int x,y;bool pd;}edge[N];
queue <int> q;


void addedge(int x,int y)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x;
}
void bfs()
{
    memset(dis,127,sizeof(dis));dis[1]=0;
    memset(vis,0,sizeof(vis));vis[1]=true;
    while (!q.empty()) q.pop();q.push(1);
    while (!q.empty())
    {
        int now=q.front();q.pop();
        for (int i=point[now];i;i=nxt[i])
            if (!vis[v[i]])
            {
                dis[v[i]]=dis[now]+1;
                vis[v[i]]=true;
                q.push(v[i]);
            }
    }
}
void _bfs(int s)
{
    while (!q.empty()) q.pop();q.push(s);
    while (!q.empty())
    {
        int now=q.front();q.pop();
        for (int i=point[now];i;i=nxt[i])
            if (dis[v[i]]==dis[now]+1&&d[v[i]]!=dis[v[i]])
            {
                d[v[i]]=d[now]+1;
                q.push(v[i]);
                cnt++;
            }
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&Q);
    for (int i=1;i<=m;++i)
    {
        scanf("%d%d",&edge[i].x,&edge[i].y);
        addedge(edge[i].x,edge[i].y);
    }
    memset(dis,127,sizeof(dis));dis[1]=0;
    bfs();
    tot=0;memset(point,0,sizeof(point));
    for (int i=1;i<=Q;++i) scanf("%d",&del[i]),edge[del[i]].pd=true;
    for (int i=1;i<=m;++i)
        if (!edge[i].pd) addedge(edge[i].x,edge[i].y);
    memset(d,127,sizeof(d));d[1]=0;
    _bfs(1);

    for (int i=Q;i>=1;--i)
    {
        int x=edge[del[i]].x,y=edge[del[i]].y;
        addedge(x,y);cnt=0;
        if (dis[x]==d[x]&&dis[y]==dis[x]+1&&dis[y]!=d[y])
            d[y]=d[x]+1,_bfs(y),++cnt;
        if (dis[y]==d[y]&&dis[x]==dis[y]+1&&dis[x]!=d[x])
            d[x]=d[y]+1,_bfs(x),++cnt;
        ans[i]=cnt;
    }
    for (int i=1;i<=Q;++i)
    {
        printf("%d\n",ans[i]);
        ans[i+1]+=ans[i];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值