【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;
}
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值