AtCoder Regular Contest 061 E - Snuke‘s Subway Trip(建图 + dijkstra最短路 / 0/1bfs / 并查集)

AtCoder Regular Contest 061 E - Snuke’s Subway Trip

problem

洛谷翻译

my idea

最近一直在做网络流,所以一读这题后,我就想到了最小费用流。

首先的问题就是建边问题。

不同线路到达同一个点从而引发后面的费用是相互独立的,不能由同一个点表示。

所以我想到了拆点,把每条边对应的两个点都分别拆出一个虚点,此虚点就代表这条边的颜色。

显然每个点只会拆出与之关联的边不同颜色的个数,而一条边只关联两个点,这最多是 O ( n + 2 m ) O(n+2m) O(n+2m) 的。

到目前为止都是可以接受的。

然后就是从一个站点到另一个站点之间的连边,发现如果要把所有情况都包含,必须一个站点的所有拆点都向另一个站点的所有拆点连边,费用为 1 / 0 1/0 1/0。别的不说,这边数就是 O ( m 2 ) O(m^2) O(m2) 级别的了。

肯定的,我们非常想把每个站点的拆点都缩成一个虚点。

颜色不同的连虚点费用 1 1 1,颜色相同就不管。然而——stuck

solution

将边当作点,正反向边分别建点,建立源汇 s , t s,t s,t

先将原图每个节点 u u u 按照出边的颜色排序,保证相同颜色的出点 v v v 是连续的一段区间。

【以下在新图中连边参与的“点”都是指把边当作点时,边的编号】

  • 正反向边之间连边,费用 0 0 0

  • 如果边起点为 1 1 1,连边 ( s , i d , 1 ) (s,id,1) (s,id,1)。因为从 s s s 开始就相当于换了公司。

  • 如果 u u u 相同的两条相邻出边 i d 1 , i d 2 id_1,id_2 id1,id2 颜色一样,那么相互连边 ( i d 1 , i d 2 , 0 ) , ( i d 2 , i d 1 , 0 ) (id_1,id_2,0),(id_2,id_1,0) (id1,id2,0),(id2,id1,0)

    因为这两条边可以免费走。这样一段连续的相同颜色的边两两之间都可以免费走了。

  • 考虑颜色转化的点,暴力连边如上面是会卡成平方的。

    新建一个虚点,对于 u u u 出边的每种颜色,随便取一条可以代表这种颜色的边向虚点连边 1 1 1

    【上一个连边知道了相同颜色的边是两两之间随便走的,所以随便选哪条连虚点都不影响。】

    虚点往这条随便选取的边连边 0 0 0

    往虚点走就证明已经换了一家公司,然后虚点走向的点就是换完后代表的公司了。

    如果不走虚点就表示不换公司。

  • 如果边终点为 n n n,连边 ( u , t , 0 ) (u,t,0) (u,t,0)

每个点只会建立一个虚点,每条边拆成正反两条边,还有源汇,总点数为 O ( n + 2 m + 2 ) O(n+2m+2) O(n+2m+2)

边数也是差不多级别的。

费用只有 0 / 1 0/1 0/1 之分,而且这个费用可以看作是边权。

0/1 bfs 可以做到线性,但是堆优化 dijkstra 跑个最短路,更板子不用脑一点直接写。

code

#include <bits/stdc++.h>
using namespace std;
#define maxn 2000005
#define inf 0x7f7f7f7f
#define Pair pair < int, int >
struct node { int v, c, id; };
vector < node > E[maxn];
vector < Pair > G[maxn];
priority_queue < Pair, vector < Pair >, greater < Pair > > q;
int n, m, s, t;
int dis[maxn], tag[maxn];

void dijkstra() {
    memset( dis, 0x7f, sizeof( dis ) );
    q.push( { dis[s] = 0, s } );
    while ( q.size() ) {
        int u = q.top().second, d = q.top().first; q.pop();
        if( dis[u] ^ d ) continue;
        for( int i = 0; i < G[u].size();i ++ ) {
            int v = G[u][i].first, w = G[u][i].second;
            if( dis[v] > dis[u] + w ) {
                dis[v] = dis[u] + w;
                q.push( { dis[v], v } );
            }
        }  
    }
}

int main() {
    scanf( "%d %d", &n, &m );
    for( int i = 1, u, v, c;i <= m;i ++ ) {
        scanf( "%d %d %d", &u, &v, &c );
        E[u].push_back( { v, c, i << 1 } );
        E[v].push_back( { u, c, i << 1 | 1 } );
    }
    s = 1, t = m * 2 + 2; int tot = t;
    for( int i = 1;i <= n;i ++ ) {
        tot ++;
        sort( E[i].begin(), E[i].end(), []( node x, node y ) { return x.c < y.c; } );
        for( int j = 0;j < E[i].size();j ++ ) {
            int k = E[i][j].v, c = E[i][j].c, id = E[i][j].id;
            if( tag[c] ^ i ) {
                tag[c] = i;
                G[tot].push_back( { id, 0 } );
                G[id].push_back( { tot, 1 } );
            }
            G[id].push_back( { id ^ 1, 0 } );
            if( i == 1 ) G[s].push_back( { id, 1 } );
            if( k == n ) G[id].push_back( { t, 0 } );
            if( j and c == E[i][j - 1].c ) 
                G[id].push_back( { E[i][j - 1].id, 0 } ), G[E[i][j - 1].id].push_back( { id, 0 } );
        }
    }
    dijkstra();
    printf( "%d\n", dis[t] == inf ? -1 : dis[t] );
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值