【魔法 / NOI】

题目


思路

动态规划:

状态定义: f [ k ] [ i ] [ j ] 对应使用了不超过 k 次魔法,从 i 到 j 的路径集合 f[k][i][j] 对应使用了不超过k次魔法,从i到j的路径集合 f[k][i][j]对应使用了不超过k次魔法,从i到j的路径集合
状态表示: 路径长度最小值 路径长度最小值 路径长度最小值
目标状态: f [ k ] [ 1 ] [ n ] f[k][1][n] f[k][1][n]
状态转移: f [ k ] [ i ] [ j ] = m i n ( f [ k − 1 ] [ i ] [ t ] + f [ 1 ] [ t ] [ j ] ) , t ∈ [ 1 , n ] f[k][i][j] = min(f[k-1][i][t]+f[1][t][j]), t \in [1,n] f[k][i][j]=min(f[k−1][i][t]+f[1][t][j]),t∈[1,n]
优化:快速幂: 将一个 k 值对应一个矩阵,代表 k 代矩阵。我们就可以将状态转移方程转化为: 将一个k值对应一个矩阵,代表k代矩阵。我们就可以将状态转移方程转化为: 将一个k值对应一个矩阵,代表k代矩阵。我们就可以将状态转移方程转化为:

f k [ i ] [ j ] = m i n ( f k − 1 [ i ] [ t ] , f 1 [ t ] [ j ] ) , t ∈ [ 1 , n ] \begin{align*} f_{k}[i][j] = min(f_{k-1}[i][t], f_{1}[t][j]), t \in [1,n] \end{align*} fk[i][j]=min(fk−1[i][t],f1[t][j]),t∈[1,n]
即该方程给出了k代矩阵的一个元素的计算方式。一个k代矩阵可以依靠一个k-1代矩阵和一个1代矩阵计算出来,类似地:
f k = f k − 1 f 1 f k = ( f k − 2 f 1 ) ( f 1 f 0 ) f k = f 1 k f 0 f 7 = f 4 ( f 2 ( f 1 f 0 ) ) \begin{align*} &f_{k} = f_{k-1}f_{1}\\ &f_{k} = (f_{k-2}f_{1})(f_{1}f_{0})\\ &f_{k} = f_{1}^{k}f_{0}\\ &f_{7} = f_{4}(f_{2}(f_{1}f_{0}))\\ \end{align*} fk=fk−1f1fk=(fk−2f1)(f1f0)fk=f1kf0f7=f4(f2(f1f0))
矩阵间的运算满足结合律,对k重运算可应用快速幂算法
f0就是对于邻接矩阵进行floyd后的矩阵d
枚举所有边进行一次魔法,尝试更新所有f0的路径,使得所有路径都最多用过一次魔法,得到f1

复杂度分析:快速幂复杂度*单个矩阵状态数量*每次状态转移的操作次数:
O ( l o g K ⋅ ( n 3 ) ) \begin{align*} O(logK \cdot (n^{3})) \end{align*} O(logK⋅(n3))


代码

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 110, M = 2510;
struct edge{
    int a;
    int b;
    int c;
} edge[M];
LL d[N][N], f[N][N];
int n, m, K;
void mul(LL c[][N], LL a[][N], LL b[][N])
{
    LL temp[N][N];
    memset(temp, 0x3f, sizeof temp);
    
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            for(int k = 1; k <= n; k++)
            {
                temp[i][j] = min(temp[i][j], a[i][k] + b[k][j]);
            }
        }
    }
    
    memcpy(c, temp, sizeof temp);
}
LL qmi()
{
    while(K)
    {
        if(K&1) mul(d, d, f);
        mul(f, f, f);
        K >>= 1;
    }
    
    return d[1][n];
}
int main()
{
    cin >> n >> m >> K;
    memset(d, 0x3f, sizeof d);
    for (int i = 1; i <= n; i ++ ) d[i][i] = 0;
    for(int i = 1; i <= m; i++)
    {
        int a, b, c;
        cin >> a >> b >> c;
        edge[i] = {a, b, c};
        d[a][b] = c;
    }
    
    for(int k = 1; k <= n; k++)
    {
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
            }
        }
    }
    memcpy(f, d, sizeof f);
    
    for(int k = 1; k <= m; k++)
    {
        int a = edge[k].a, b = edge[k].b, c = edge[k].c;
        
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                f[i][j] = min(f[i][j], d[i][a] - c + d[b][j]);
            }
        }
    }
    
    cout << qmi();
    return 0;
}
相关推荐
_feivirus_38 分钟前
神经网络_使用TensorFlow预测气温
人工智能·神经网络·算法·tensorflow·预测气温
大柏怎么被偷了1 小时前
【C++算法】位运算
开发语言·c++·算法
程序猿方梓燚1 小时前
C/C++实现植物大战僵尸(PVZ)(打地鼠版)
c语言·开发语言·c++·算法·游戏
CPP_ZhouXuyang1 小时前
C语言——模拟实现strcpy
c语言·开发语言·数据结构·算法·程序员创富
闻缺陷则喜何志丹1 小时前
【C++前后缀分解 动态规划】2100. 适合野炊的日子|1702
c++·算法·动态规划·力扣·前后缀分解·日子·适合
逝去的秋风1 小时前
【代码随想录训练营第42期 Day57打卡 - 图论Part7 - Prim算法与Kruskal算法
算法·图论·prim算法
QXH2000001 小时前
数据结构—双向链表
c语言·数据结构·算法·链表
旺小仔.2 小时前
【数据结构篇】~排序(1)之插入排序
c语言·数据结构·算法·链表·性能优化·排序算法
绎岚科技2 小时前
深度学习自编码器 - 随机编码器和解码器篇
人工智能·深度学习·算法·机器学习
jingling5552 小时前
后端开发刷题 | 数字字符串转化成IP地址
java·开发语言·javascript·算法