魔法

魔法

C 国由 n 座城市与 m 条有向道路组成,城市与道路都从 1 开始编号,经过 i 号道路需要 ti 的费用。

现在你要从 1 号城市出发去 n 号城市,你可以施展最多 K 次魔法,使得通过下一条道路时,需要的费用变为原来的相反数,即费用从 ti 变为 ti

请你算一算,你至少要花费多少费用才能完成这次旅程。

注意:使用魔法只是改变一次的花费,而不改变一条道路自身的 ti

最终的费用可以为负,并且一个城市可以经过多次(包括 n 号城市)。

输入格式

第一行三个整数 n,m,K,表示城市数、道路数、魔法次数限制。

接下来 m 行每行三个整数 ui,vi,ti,第 i 行描述 i 号道路,表示一条从 uivi 的有向道路,经过它需要花费 ti

输出格式

仅一行一个整数表示答案。

数据范围

对于所有测试点和样例满足:
1n100,1m2500,0K106,1ui,vin,1ti109

数据保证图中无自环,无重边,至少存在一条从 1 号城市到达 n 号城市的路径。
每个测试点的具体限制见下表。

测试点编号 n m K 特殊限制
12 5 20 0
34 10 20 50
56 10 20 0
78 20 200 50 图中无环
910 20 200 0
1112 100 200 50 图中无环
1314 100 200 50
1518 100 2500 1000
1920 100 2500 106

输入样例1:

4 3 2
1 2 5
2 3 4
3 4 1

输出样例1:

-8

样例1解释

依次经过 1 号道路、2 号道路、3 号道路,并在经过 12 号道路前使用魔法。

输入样例2:

2 2 2
1 2 10
2 1 1

输出样例2:

-19

样例2解释

依次经过 1 号道路、2 号道路、1 号道路,并在两次经过 1 号道路前都使用魔法。

 

解题思路

  n最大取值为100,又是求最短路的问题,这提示我们可以往Floyd的方向去想。

  定义状态f(k,i,j)表示所有从i走到j最多使用k次魔法的方案的集合,属性就是最短距离。根据从ij的路径中所经过的点u来把路径分成两段,其中前面一段iu使用魔法次数不超过k1次,后面一段uj使用魔法次数不超过1次,这样就得到状态转移方程f(k,i,j)=min1un{f(k1,i,u)+f(1,u,j)}

  直接这样做的话时间复杂度为O(kn3),肯定会超时。注意到状态转移方程中只涉及到min+运算,具有结合律的性质,可以用类似于快速幂求矩阵乘法的做法来求f(k,i,j)。具体请参考这篇博客:min 与 + 运算转换成类似于矩阵乘法的推导过程

  与上面博客的思路一样,我们把f(k,i,j)看成是矩阵Fn×nki行第j的元素,因此上面的状态转移方程就变成了Fi,jk=min1un{Fi,uk1+Fu,j1}

  其中Fk是一个n×n的矩阵,里面的每一个元素Fi,jk表示从ij使用魔法不超过k次的最短路径。矩阵间的运算就是Fk=Fk1 F1,通过递推可以发现有Fk=(F1)k。由于具有结合律,因此可以用快速幂来解决。

  那么F1怎么求呢?根据定义矩阵F1中任意一个元素Fi,j1表示ij使用魔法不超过1次的最短路径。这里有种暴力的解决方法,就是先求出任意两点间不使用魔法的最短路径,也就是F0,这个直接跑Floyd就可以了。然后再枚举每一条边,把这条边的权值取相反数,假设这条边原来的权值为wu,v,那么对于任意两点ij,强行走这条边iuvj,这条路径就对wu,v使用了一次魔法,看看路径长度是否比不使用魔法要短就可以了。因此有Fi,j1=min{Fi,j0, Fi,u0wu,v+Fv,j0}

  AC代码如下,时间复杂度为O(n3logk)

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 110, M = 2510, INF = 0x3f3f3f3f;
 7 
 8 int n, m, k;
 9 LL f[N][N], g[N][N], tmp[N][N];
10 struct Node {
11     int v, w, wt;
12 }e[M];
13 
14 void mul(LL c[][N], LL a[][N], LL b[][N]) {
15     memset(tmp, 0x3f, sizeof(tmp));
16     for (int i = 1; i <= n; i++) {
17         for (int j = 1; j <= n; j++) {
18             for (int k = 1; k <= n; k++) {
19                 tmp[i][j] = min(tmp[i][j], a[i][k] + b[k][j]);
20             }
21         }
22     }
23     memcpy(c, tmp, sizeof(tmp));
24 }
25 
26 int main() {
27     scanf("%d %d %d", &n, &m, &k);
28     for (int i = 1; i <= n; i++) {
29         for (int j = 1; j <= n; j++) {
30             g[i][j] = INF;
31         }
32         g[i][i] = 0;
33     }
34     for (int i = 0; i < m; i++) {
35         int v, w, wt;
36         scanf("%d %d %d", &v, &w, &wt);
37         e[i] = {v, w, wt};
38         g[v][w] = wt;
39     }
40     // 一开始跑Floyd,得到的g就是F^0
41     for (int k = 1; k <= n; k++) {
42         for (int i = 1; i <= n; i++) {
43             for (int j = 1; j <= n; j++) {
44                 g[i][j] = min(g[i][j], g[i][k] + g[k][j]);
45             }
46         }
47     }
48     memcpy(f, g, sizeof(g));
49     // 求F^1
50     for (int k = 0; k < m; k++) {
51         for (int i = 1; i <= n; i++) {
52             for (int j = 1; j <= n; j++) {
53                 f[i][j] = min(f[i][j], g[i][e[k].v] + g[e[k].w][j] - e[k].wt);
54             }
55         }
56     }
57     // 求(F^1)^k
58     while (k) {
59         if (k & 1) mul(g, g, f);
60         mul(f, f, f);
61         k >>= 1;
62     }
63     printf("%lld", g[1][n]);
64     
65     return 0;
66 }
复制代码

 

参考资料

  AcWing 1426. 魔法(蓝桥杯集训·每日一题):https://www.acwing.com/video/4677/

posted @   onlyblues  阅读(31)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
历史上的今天:
2022-03-30 翻转树边
2021-03-30 是否同一棵二叉搜索树
Web Analytics
点击右上角即可分享
微信分享提示