题解:【ABC291F】 Teleporter and Closed off
给定一个 \(n\) 个点的图,每个点只向编号最多大于它 \(m\) 的点连单向边,求不经过 \(2 \sim n\) 中的一个点,\(1 \to n\) 的最短路。注意到 \(m\) 很小,这里给出两种做法,都是基于 \(m\) 的特性来做的。
第一种是直接搜索。首先我们求出 \(1\) 到每个点的最短路和 \(n\) 到每个点的最短路。如果说不经过点 \(i\),对于点 \(j\) 有 \(j+k>i (1 \le k \le m,j+k \le n)\) 且有边 \(j \to j+k\) 那就说明存在这条合法路径,直接枚举 \(j\) 和 \(k\) 即可,统计的答案即为 \(dis_{1 \to j} + dis_{j+k \to n} +1\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace LgxTpre
{
static const int MAX=100010;
static const int INF=4557430888798830399;
static const int mod=998244353;
int n,m,ans;
string s[MAX];
struct edge
{
int nex,to;
}e[MAX<<5];
int head[MAX],cnt;
inline void add(int x,int y)
{
e[++cnt].nex=head[x],head[x]=cnt,e[cnt].to=y;
return;
}
queue<int> q;
int dis[MAX][2];
inline void bfs(int dir)
{
if(!dir) q.push(1),dis[1][0]=0;
else q.push(n),dis[n][1]=0;
while(!q.empty())
{
int now=q.front(); q.pop();
for(int i=head[now];i;i=e[i].nex)
{
int to=e[i].to;
if(dis[to][dir]>dis[now][dir]+1)
dis[to][dir]=dis[now][dir]+1,q.push(to);
}
}
return;
}
inline void init()
{
memset(head,0,sizeof head);
cnt=0;
}
inline void lmy_forever()
{
cin>>n>>m;
memset(dis,0x3f,sizeof dis);
for(int i=1;i<=n;++i) cin>>s[i];
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(s[i][j-1]=='1')
add(i,i+j);
bfs(0);
init();
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(s[i][j-1]=='1')
add(i+j,i);
bfs(1);
for(int i=2;i<n;++i)
{
ans=INF;
for(int j=max(1ll,i-m+1);j<i;++j)
for(int k=1;k<=m;++k)
if(j+k>i&&s[j][k-1]=='1')
ans=min(ans,dis[j][0]+dis[j+k][1]+1);
if(ans==INF) cout<<-1<<" ";
else cout<<ans<<" ";
}
return;
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
LgxTpre::lmy_forever();
return (0-0);
}
第二种是矩阵。记 \(d_i\) 为 \(1 \to i\) 的最小花费,设计状态矩阵:
\[\begin{bmatrix}
d_i \; d_{i-1} \; d_{i-2} \cdots d_{i-m}
\end{bmatrix}
\]
将原先的矩阵乘法(乘和加)重新定义为加和取 \(\min\),显然是满足结合律的,即:
\[a_{i,j} = \min_{1 \le k \le m} b_{i,k} + c_{k,j}
\]
我们记 \(k\) 为第几个转移矩阵,首先初始化:
\[Base_{k,i,j} =
\begin{cases}
0 \; (j=i+1) \\
\infty \; \text{otherwise}
\end{cases}
\]
如果存在 \(x \to x+i\) 的边,我们令 \(Base_{x+i,i,1}=1\),即为可以以 \(1\) 花费从 \(i\) 直接到 \(i+x\)。
考虑矩阵的意义,因为是取 \(\min\) 运算,所以极大值必定取不到,含义就是不存在这种边;\(0\) 即为 \(d_{i-1} \sim d_{i-m}\) 的状态的继承。然后我们发现不经过第几个点实际上就是不进行第几个转移矩阵的操作,所以我们对转移矩阵求前缀积和后缀积,就可以线性得到答案了。
由于我的矩阵自带大常数的问题,相比于搜索处于极大的劣势。
#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace LgxTpre
{
static const int MAX=100010;
static const int INF=4557430888798830399;
static const int mod=998244353;
int n,m,ans;
string s[MAX];
struct Matrix
{
static const int qck=15;
int a[qck][qck];
Matrix operator * (const Matrix& T) const
{
Matrix res; memset(res.a,0x3f,sizeof res.a);
for(int i=1;i<=m;++i)
for(int j=1;j<=m;++j)
for(int k=1;k<=m;++k)
res.a[i][j]=min(res.a[i][j],a[i][k]+T.a[k][j]);
return res;
}
}pre[MAX],suf[MAX],bas[MAX];
inline void lmy_forever()
{
cin>>n>>m;
for(int i=1;i<=n;++i)
cin>>s[i];
for(int i=0;i<=n;++i)
for(int j=1;j<=m;++j)
for(int k=1;k<=m;++k)
bas[i].a[j][k]=(k==j+1)?0:INF;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
if(s[i][j-1]=='1')
bas[i+j].a[j][1]=1;
pre[2]=bas[2];
for(int i=3;i<=n;++i) pre[i]=pre[i-1]*bas[i];
suf[n]=bas[n];
for(int i=n-1;i>=1;--i) suf[i]=bas[i]*suf[i+1];
for(int i=2;i<n;++i)
{
if(i==2) ans=(bas[0]*suf[i+1]).a[1][1];
else ans=(pre[i-1]*bas[0]*suf[i+1]).a[1][1];
if(ans==INF) cout<<-1<<" ";
else cout<<ans<<" ";
}
return;
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
LgxTpre::lmy_forever();
return (0-0);
}