这篇文章上次修改于 464 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
本文共 1627 个字,阅读时长 ≈ 5 分钟
BZOJ 2165. 大楼
题意
从第0层开始有无穷层,每层有n个房间,给定矩阵A,$A_{i,j}$ 表示从第x层的房间 i 可以跳到第 $x+A_{i,j}$ 层的房间 j(x任意),$A_{i,j}=0$ 表示不能跳。初始在第0层第1个房间,求最少跳几次可以到达 $\geq m$ 层。
思路
首先想直接暴力枚举最后答案,但一看 $m\leq 1e18$ 暴力枚举直接爆炸,想想怎么加速枚举,倍增,枚举 $k$ 代表跳 $2^k$ 次,加上起点终点我们可以设出一个 $f[k][i][j]$ 表示跳了 $2^k$ 步,最后一步从点 i 跳到点 j 能跳到的最大高度。
以下内容节选自此博客:
由于我们从任意一个 $i$ 递推到 $i−1$ 进行的都是相同的操作,而整个递推过程具备单调性,根据转移方程定义的运算满足结合律,因此我们可以采用一种类似于矩阵乘法的倍增算法。
定义状态 $g(p,i,j)$ 表示从房间 $i$ 走到 $j$,走不超过 $2p$ 步到达的最高层数。转移如下:
$g(p,i,j)=\begin{cases}g(p-1,i,j)\\\max\limits_{k=1}^n\Big\{g(p-1,i,k)+g(p-1,k,j)\Big\}\end{cases}\Bigg\}$
由于所有的边消耗的步数都为一步,因此上述倍增算法一定可以包含不超过 $2p$ 的所有情况。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<climits>
using namespace std;
typedef long long ll;
ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<3ll)+(x<<1ll)+(c^48);
c=getchar();
}
return x*f;
}
const int N=102;
const ll inf=1ll<<60;
int T,n,h,t,cnt;
ll ans,m;
struct jz{
ll mt[N][N];
jz(){
for(int i=0;i<N-1;++i) for(int j=0;j<N-1;++j) mt[i][j]=0;
}
jz operator *(const jz &x)const{
jz c;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
c.mt[i][j]=-inf;
for(int k=1;k<=n;++k)
c.mt[i][j]=max(c.mt[i][j],mt[i][k]+x.mt[k][j]);
}
return c;
}
}a,f[N];
bool check(jz x){
for(int i=1;i<=n;++i) if(x.mt[1][i]>=m) return 1;
return 0;
}
int main()
{
T=(int)read();
while(T--){
n=(int)read();
m=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
//初始化+读入
f[1].mt[i][j]=read();
if(!f[1].mt[i][j]) f[1].mt[i][j]=-inf;
}
ans=1;
for(cnt=1;cnt<N-1;++cnt){
//倍增预处理
f[cnt+1]=f[cnt]*f[cnt];
if(check(f[cnt+1])) break;
}
a=f[1];
bool flag=0;
for(int i=cnt;i;--i){
//统计答案
jz b=a*f[i];
if(!check(b)){
a=b;
ans+=(1ll<<(i-1));
}
else flag=1;
}
if(flag) printf("%lld\n",ans+1);
else printf("-1\n");
}
return 0;
}
已有 2 条评论
过来看看~
@TeacherDu 好捏|´・ω・)ノ