博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【做题】HDU6331 Walking Plan——矩阵&分块
阅读量:5038 次
发布时间:2019-06-12

本文共 2485 字,大约阅读时间需要 8 分钟。

题意:给出一个有\(n\)个结点的有向图,边有边权。有\(q\)组询问,每次给出\(s,t,k\),问从\(s\)\(t\)至少经过\(k\)条边的最短路。

\(n \leq 50, \, q \leq 10^5, \, k \leq 10^4\)

首先,注意到\(n\)非常小这个性质。对于很多这类点数少,询问不易维护也不复杂的图论题,可以用矩阵来做。

我们设原图的邻接矩阵为\(G\),并定义矩阵的二元运算\(\bigotimes\)为:

\[ (A \bigotimes B)_{ij} = \min_{k} A_{ik} + B_{kj} \]
那么,令\(A^k = \underbrace{A \bigotimes A \cdots A}_{k\text{ times}}\),那么\((G^k)_{ij}\)就等于从\(i\)\(j\)恰好走\(k\)步的最短路。因此,每次询问的答案就是\(G^k \bigotimes S\),其中\(S\)为图floyd后得到的邻接矩阵。

接下来,让我们考虑这样一个暴力:一开始对于所有可能的路径长度\(l \space (l \leq k + n )\),求出\(G^l \bigotimes S\)。这样预处理是\(O(n^3 k)\),询问是\(O(1)\)

这样做的话,预处理复杂过高,但每次询问能允许更高的复杂度(\(O(n)\))。注意到,询问要求的只是一个矩阵上的一个元素的罢了。因此,如果我们能把所有\(G^k \bigotimes S\)都表示为\(A \bigotimes B\)的形式,并且所有可能的\(A\)\(B\)的总数量可以接受,就可以了。这也相当于把所有\(k\)拆分为两个数。

答案是分块。令块大小为\(H(k)\),那么我们可以把\(k\)分为\(\left\lfloor \frac {k} {H(k)} \right\rfloor \times H(k) + k \mod H(k)\)的形式。因此,我们令\(H(k) = \sqrt k\),那么,只要求出所有\(A_i = G^{i\sqrt k}, \ B_i = G_i \bigotimes S\),就可以\(O(n)\)回答每次询问,且\(|A| = |B| = \sqrt k\),故预处理复杂度也能达到\(O(n^3 \sqrt k)\)

时间复杂度\(O(n^3 \sqrt k + nq)\)

#include 
using namespace std;const int N = 55, BAS = 100, INF = 0x3f3f3f3f;typedef int mat[N][N];int n,m,q;mat a[N * 3],b[N * 3];void mul(mat& x,mat y,mat z) { memset(x,0x3f,sizeof(mat)); for (int k = 1 ; k <= n ; ++ k) for (int i = 1 ; i <= n ; ++ i) for (int j = 1 ; j <= n ; ++ j) x[i][j] = min(x[i][j],y[i][k] + z[k][j]);}int main() { int T,x,y,z,ret; scanf("%d",&T); while (T --) { scanf("%d%d",&n,&m); memset(a,0x3f,sizeof a); memset(b,0x3f,sizeof b); for (int i = 1 ; i <= m ; ++ i) { scanf("%d%d%d",&x,&y,&z); b[1][x][y] = min(b[1][x][y],z); } for (int i = 1 ; i <= n ; ++ i) a[0][i][i] = 0, b[0][i][i] = 0; for (int i = 2 ; i <= BAS + n ; ++ i) mul(b[i],b[i-1],b[1]); for (int i = 1 ; i <= n ; ++ i) for (int j = 1 ; j <= n ; ++ j) a[1][i][j] = b[100][i][j]; for (int i = 2 ; i <= BAS ; ++ i) mul(a[i],a[i-1],a[1]); for (int k = BAS + n - 1 ; k >= 0 ; -- k) for (int i = 1 ; i <= n ; ++ i) for (int j = 1 ; j <= n ; ++ j) b[k][i][j] = min(b[k][i][j],b[k+1][i][j]); scanf("%d",&q); for (int i = 1 ; i <= q ; ++ i) { scanf("%d%d%d",&x,&y,&z); ret = INF; for (int k = 1 ; k <= n ; ++ k) ret = min(ret,a[z/BAS][x][k] + b[z%BAS][k][y]); if (ret != INF) printf("%d\n",ret); else puts("-1"); } } return 0;}

小结:感觉分块白学了……还是只会死板地使用。

转载于:https://www.cnblogs.com/cly-none/p/9398151.html

你可能感兴趣的文章
爬取:中国大学排名
查看>>
聊天室(C++客户端+Pyhton服务器)_1.框架搭设
查看>>
UpdatePanel 内控件 更新“外的”控件【转】
查看>>
mybatis中&gt;=和&lt;=的实现方式
查看>>
Python面向对象03/继承
查看>>
java序列化和反序列化
查看>>
绝对定位
查看>>
flink源码编译(windows环境)
查看>>
dpkg 删除 百度网盘 程序
查看>>
服务器nginx安装
查看>>
std::nothrow
查看>>
rest-framework 分页器
查看>>
JQuery(一)安装&选择器 样式篇
查看>>
浏览器的DNS缓存查看和清除
查看>>
浏览器跨域问题
查看>>
HTML5 input控件 placeholder属性
查看>>
使用JAVA如何对图片进行格式检查以及安全检查处理
查看>>
html5实现移动端下拉刷新(原理和代码)
查看>>
iPhone开发中从一个视图跳到另一个视图有三种方法:
查看>>
pytho logging
查看>>