Luogu

分析

显然最短路只有三种可能:

  • 全走 $a$ 边。
  • 把两条 $a$ 边看成一条 $b$ 边,如果还剩一条 $a$ 边就走 $a$ 边。
  • 全走 $b$ 边。

前两种只需要简单 0/1 BFS 一遍即可,关键在于第三种如何处理。

有一个显然的 $\mathcal{O}(m^2)$ 思路是,枚举 $u$ 的出边 $v$ 和 $v$ 的出边 $w$,如果 $u,w$ 间没有连边就用 $u$ 更新 $w$。

这个东西感觉和三元环计数有一点点像,因此我们考虑怎么优化。我们维护两个边集,一个就是原图,另一个一开始是原图但要支持删边。我们枚举 $u$,然后枚举第一个边集中它的出边 $v$ 并标记,然后枚举第二个边集中 $v$ 的出边 $w$,如果 $w$ 没有被标记就更新 $w$ 然后把 $(v,w)$ 从第二个边集中删掉。

这样子复杂度是 $\mathcal{O}(m\sqrt{m})$ 的,因为三元环数量是 $\mathcal{O}(m\sqrt{m})$ 的,而不在三元环中的边都只会被删一次。

代码

// ====================================
//   author: M_sea
//   website: https://m-sea-blog.com/
// ====================================
#include <bits/stdc++.h>
#define file(x) freopen(#x".in","r",stdin); freopen(#x".out","w",stdout)
#define debug(...) fprintf(stderr,__VA_ARGS__)
using namespace std;
typedef long long ll;

int read() {
    int X=0,w=1; char c=getchar();
    while (c<'0'||c>'9') { if (c=='-') w=-1; c=getchar(); }
    while (c>='0'&&c<='9') X=X*10+c-'0',c=getchar();
    return X*w;
}

const int N=100000+10;

int n,m,s,a,b,ans[N],dis[N],vis[N];
vector<int> E[N];
unordered_set<int> S[N];
queue<int> Q;

int main() {
    n=read(),m=read(),s=read(),a=read(),b=read();
    for (int i=1;i<=m;++i) {
        int u=read(),v=read();
        E[u].emplace_back(v),E[v].emplace_back(u);
        S[u].insert(v),S[v].insert(u);
    }
    memset(dis,-1,sizeof(dis)); dis[s]=0,Q.push(s);
    while (!Q.empty()) {
        int u=Q.front(); Q.pop();
        for (int v:E[u])
            if (!~dis[v]) dis[v]=dis[u]+1,Q.push(v);
    }
    for (int i=1;i<=n;++i)
        ans[i]=min(dis[i]*a,(dis[i]>>1)*b+(dis[i]&1)*a);
    memset(dis,-1,sizeof(dis)); dis[s]=0,Q.push(s);
    while (!Q.empty()) {
        int u=Q.front(); Q.pop();
        for (int v:E[u]) vis[v]=1;
        for (int v:E[u]) {
            vector<int> del;
            for (int w:S[v]) {
                if (vis[w]) continue;
                if (!~dis[w]) dis[w]=dis[u]+1,Q.push(w);
                del.emplace_back(w);
            }
            for (int w:del) S[v].erase(w);
        }
        for (int v:E[u]) vis[v]=0;
    }
    for (int i=1;i<=n;++i) if (~dis[i]) ans[i]=min(ans[i],dis[i]*b);
    for (int i=1;i<=n;++i) printf("%d\n",ans[i]);
    return 0;
}
最后修改:2020 年 10 月 09 日 04 : 44 PM