HDU 6331 Walking Plan(分块 动态规划)

题目链接:Walking Plan

题意

给一个 n n 个节点 m 条边的有向图,第 i i 条边的两个端点为 ui,vi,边的长度为 wi w i q q 次询问,每次询问从节点 s t t 至少走过 k 条路径的最小距离。

输入

第一行包含一个整数 T (1T10) T   ( 1 ≤ T ≤ 10 ) ,接下去有 T T 组数据,每组数据第一行为两个整数 n,m (2n50,1m104),接下去 m m 行每行三个整数 ui,vi,wi (1ui,vin,uivi,1wi104),接着为一个整数 q (1q105) q   ( 1 ≤ q ≤ 10 5 ) ,接下去 q q 行每行三个整数 si,ti,ki (1si,tin,1k104)

输出

对于每次询问,输出最短路径长度,如果无法从节点 si s i 到达 ti t i ,则输出 1 − 1

样例

输入
2
3 3
1 2 1
2 3 10
3 1 100
3
1 1 1
1 2 1
1 3 1
2 1
1 2 1
1
2 1 1
输出
111
1
11
-1
题解

定义 G[i][j] G [ i ] [ j ] 为从节点 i i 恰好经过 1 步到达节点 j j 的最短距离(即原图按输入取最小值),无法到达设为 dis[k][i][j] d i s [ k ] [ i ] [ j ] 表示从 i i 点出发恰好经过 k 步到达 j j 点的最短路径,则有递推式:

dis[0][i][j]={0i=jijdis[k][i][j]=min(dis[k1][i][u]+G[u][j]),u[1,n],k0
定义 A[x][i][j] A [ x ] [ i ] [ j ] 表示从 i i 恰好经过 100x 步到达 j j 的最短距离,B[x][i][j] 表示从 i i 最少经过 x 次到达 j j 的最短距离,这样 k 就可以分为 k100 ⌊ k 100 ⌋ k%100 k % 100 两个部分,通过枚举中转点得到答案:

ans=min(A[k100][i][u]+B[k%100][u][j]),u[1,n] a n s = min ( A [ ⌊ k 100 ⌋ ] [ i ] [ u ] + B [ k % 100 ] [ u ] [ j ] ) , u ∈ [ 1 , n ]
第一部分可以直接从上面的递推式得到,第二部分可以在原图上令 G[i][i]=0,i[1,n] G [ i ] [ i ] = 0 , i ∈ [ 1 , n ] 后跑一遍 floyd f l o y d ,再将从 i i 恰好经过 k 步到达 j j 的最短距离距离 dis[k][i][j] 通过 G[i][j] G [ i ] [ j ] 转化为 B[x][i][j] B [ x ] [ i ] [ j ]
B[k][i][j]=min(dis[k][i][u],G[u][j]),u[1,n] B [ k ] [ i ] [ j ] = min ( d i s [ k ] [ i ] [ u ] , G [ u ] [ j ] ) , u ∈ [ 1 , n ]

过题代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#include <algorithm>
#include <functional>
#include <iomanip>
using namespace std;

#define LL long long
const int Size = 51;
const int maxn = 101;
int n;
struct Matrix {
    int num[Size][Size];

    void Init() {
        for(int i = 1; i <= n; ++i) {
            memset(num[i], 0x3f, sizeof(int) * (n + 1));
        }
    }

    void Set_zero() {
        for(int i = 1; i <= n; ++i) {
            num[i][i] = 0;
        }
    }

    void operator=(const Matrix &m) {
        for(int i = 1; i <= n; ++i) {
            memcpy(num[i], m.num[i], sizeof(int) * (n + 1));
        }
    }

    void Combine(const Matrix &m, Matrix &ans) {
        Matrix ret;
        ret.Init();
        for(int i = 1; i <= n; ++i) {
            for(int j = 1; j <= n; ++j) {
                for(int k = 1; k <= n; ++k) {
                    ret.num[i][j] = min(ret.num[i][j], num[i][k] + m.num[k][j]);
                }
            }
        }
        ans = ret;
    }

    void floyd() {
        for(int k = 1; k <= n; ++k) {
            for(int i = 1; i <= n; ++i) {
                for(int j = 1; j <= n; ++j) {
                    num[i][j] = min(num[i][j], num[i][k] + num[k][j]);
                }
            }
        }
    }
};

int T, m, u, v, dis, q, k, INF, ans;
Matrix A[maxn], B[maxn], G;

int main() {
    #ifdef LOCAL
    freopen("test.txt", "r", stdin);
//    freopen("test1.out", "w", stdout);
    #endif // LOCAL
    ios::sync_with_stdio(false);

    memset(&INF, 0x3f, sizeof(int));
    scanf("%d", &T);
    while(T--) {
        scanf("%d%d", &n, &m);
        G.Init();
        for(int i = 0; i < m; ++i) {
            scanf("%d%d%d", &u, &v, &dis);
            G.num[u][v] = min(G.num[u][v], dis);
        }
        A[0].Init();
        A[0].Set_zero();
        B[0].Init();
        B[0].Set_zero();
        for(int i = 1; i < maxn; ++i) {
            B[i - 1].Combine(G, B[i]);
        }
        for(int i = 1; i < maxn; ++i) {
            A[i - 1].Combine(B[100], A[i]);
        }
        G.Set_zero();
        G.floyd();
        for(int i = 0; i < maxn; ++i) {
            G.Combine(B[i], B[i]);
        }
        scanf("%d", &q);
        while(q--) {
            scanf("%d%d%d", &u, &v, &k);
            ans = INF;
            for(int i = 1; i <= n; ++i) {
                ans = min(ans, A[k / 100].num[u][i] + B[k % 100].num[i][v]);
            }
            if(ans == INF) {
                printf("-1\n");
            } else {
                printf("%d\n", ans);
            }
        }
    }

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值