【Codeforces 576D】 Flights for Regular Customers

题目链接

题目大意

给出一张有向图,对于边 i 有限制条件di,表示在走边 i 前必须走过至少di条其他的边。为从点 1 到点n最少要走几条边。

题解

比较容易想到的暴力是 fi,j 表示在走 j 步能不能正好到i点。那么 fi,j 可以转移到 fk,j+1 k 为与i间有边 e dej
这种做法在 di 很小的时候才可以通过。所以说貌似没什么用。
考虑以前DNA Sequence这类题目用到过的思路,邻接矩阵的 k 次方等于i正好走 k 步到j的方案数。考虑边的限制条件,如果我们现在已经走过了 k 步,那么我们能用的边只有dik的所有的边。我们就可以把边按照 di 升序排序,将 di 按顺序加入边集,分段通过矩阵快速幂转移。维护点与点之间走 k 步的到达情况。注意这里我们只需要知道能不能走到而并不需要知道方案数,那么我们的矩阵就永远是01矩阵。用朴素的矩阵乘法在这题会T掉,所以用bitset加速。加速后矩阵乘法如下:

void mul(bitset<N> *a, bitset<N> *b){
    bitset<N> ret[N];
    rep(i, 1, n) rep(j, 1, n)
        if(a[i][j]) ret[i] |= b[j];
    rep(i, 1, n) a[i] = ret[i];
}

bitset<N> a[N], b[N];
mul(a, b);

思路是,如果i能走到 j ,那么j能走到的所有点 i 都能走到。
在枚举边的时候通过Floyd维护最短路。如果在某个时刻走了 k 步,1可以到达 j j n <script type="math/tex" id="MathJax-Element-35">n</script>有最短路。那么就可以更新答案。

#include<bits/stdc++.h>
using namespace std;
using std::bitset;

#define rep(i, l, r) for(register int i = (l); i <= (r); i++)

const int N = 150 + 10, inf = 0x7fffffff;
int n, m, ans;
int d[N][N];
struct Edge{
    int u, v, w;
    bool operator < (const Edge &rhs) const{
        return w < rhs.w;
    }
}edg[N];

bitset<N> ari[N], g[N];

void mul(bitset<N> *a, bitset<N> *b){
    bitset<N> ret[N];
    rep(i, 1, n) rep(j, 1, n)
        if(a[i][j]) ret[i] |= b[j];
    rep(i, 1, n) a[i] = ret[i];
}

void poww(bitset<N> *a, int k){
    bitset<N> ret[N];
    rep(i, 1, n) ret[i][i] = 1;
    while(k){
        if(k & 1) mul(ret, a);
        mul(a, a);
        k >>= 1;
    }
    rep(i, 1, n) a[i] = ret[i];
}

void work(){
    bitset<N> tmp[N];
    int now = 0; ans = inf;
    memset(d, 0x3f, sizeof(d));
    for(int i = 1; i <= n; i++) d[i][i] = 0, ari[i][i] = 1;
    rep(i, 1, m){
        int u = edg[i].u, v = edg[i].v, w = edg[i].w;
        rep(j, 1, n) rep(k, 1, n)
            d[j][k] = min(d[j][k], d[j][u] + 1 + d[v][k]);
        rep(j, 1, n) tmp[j] = g[j];
        poww(tmp, w - now);
        mul(ari, tmp);
        rep(j, 1, n)
            if(ari[1][j] && d[j][n] != 1061109567){
                ans = min(ans, w + d[j][n]);
            }
        now = w;
        g[u][v] = 1;
    }
    if(ans == inf) puts("Impossible");
    else printf("%d\n", ans);
}

void init(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++)
        scanf("%d%d%d", &edg[i].u, &edg[i].v, &edg[i].w);
    sort(edg + 1, edg + m + 1);
}

int main(){
    init();
    work();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值