​也许更好的阅读体验​

D e s c r i p t i o n \mathcal{Description} Description

给定一张n个点m条边的带权有向图,每条边的边权只可能是1,2,3中的一种。将所有可能的路径按路径长度排序,请输出第k小的路径的长度,注意路径不一定是简单路径,即可以重复走同一个点。

( 1 ≤ n ≤ 40 , 1 ≤ m ≤ 1000 , 1 ≤ k ≤ 1 0 18 ) (1\leq n\leq 40,1\leq m\leq 1000,1\leq k\leq 10^{18}) (1n401m10001k1018)

S o l u t i o n \mathcal{Solution} Solution

死毒瘤题,打了一晚上,最后把自己的方法改的和其他人差不多了

边权不为 1 1 1,不好直接套用邻接矩阵,考虑把一个点拆开,让一条长度大于 1 1 1的路径要到一些没用的点使得它需要走多次才能走到它该到的点

把一个点 n n n拆为 n 0 , n 1 , n 2 n_0,n_1,n_2 n0,n1,n2,分别表示距离 n n n还有 0 / 1 / 2 0/1/2 0/1/2的距离,实际上, n 0 n_0 n0就是原本的点,我们称之为实点, n 1 , n 2 n_1,n_2 n1,n2则是用来消耗路径长度的点,称之为虚点

先有 n 2   →   n 1 , n 1   →   n 0 n_2\ \rightarrow\ n_1,n_1\ \rightarrow \ n_0 n2  n1,n1  n0连边

对一条边 ( u , v , w ) \left(u,v,w\right) (u,v,w),考虑由实点 u 0 u_0 u0走出去一步,那么距离 v v v就会减一,所以 u 0 u_0 u0 v w − 1 v_{w-1} vw1连一条边,即连边 ( u 0 , v w − 1 ) \left(u_0,v_{w-1}\right) (u0,vw1)

现在我们可以对现在的矩阵做乘法和快速幂了,每个实点到实点的权值就是方案数

如现在是这个矩阵的 k k k次方,则矩阵上 ( u 0 , v 0 ) \left(u_0,v_0\right) (u0,v0)上的权值表示的就是从 u 0 u_0 u0出发,走 k k k步走到 v 0 v_0 v0的方案数

接下来考虑怎么求答案, k k k很大,自然地就想到了倍增,类似求 L C A LCA LCA一样的去确定答案即可

当然,直接对矩阵求 p p p次方,那么求出来的矩阵里的值表示的是刚好走 p p p步从某个位置走到某个位置的答案

为了方便的进行判断,我们需要把矩阵的值表示成走小于等于 p p p步的方案数

所以要考虑把走过的答案存下来,我们可以用 0 0 0号点表示所有的方案数

怎么用 0 0 0号点表示呢,考虑每次算出一个值后再算下一个值时将当前的答案放到 0 0 0号点去

所以对每个点实点 u 0 u_0 u0连一条 ( u 0 , 0 ) \left(u_0,0\right) (u0,0)的边即可,而每次计算不能把上次的答案丢了,所以还要连一条 ( 0 , 0 ) \left(0,0\right) (0,0)的边

这样当前矩阵的 0 0 0号点就表示着上一次的答案

我们再弄一个矩阵乘上当前矩阵就可以表示,这个矩阵 0 0 0向所有的实点连一条边即可

C o d e \mathcal{Code} Code

/*******************************
Author:Morning_Glory
LANG:C++
Created Time:2019年11月06日 星期三 18时58分03秒
*******************************/
#include <cstdio>
#include <fstream>
#define ll long long
#define ld long double
#define rint register int
using namespace std;
const int maxn = 130;
//{{{cin
struct IO{
template<typename T>
IO & operator>>(T&res){
res=0;
bool flag=false;
char ch;
while((ch=getchar())>'9'||ch<'0') flag|=ch=='-';
while(ch>='0'&&ch<='9') res=(res<<1)+(res<<3)+(ch^'0'),ch=getchar();
if (flag) res=~res+1;
return *this;
}
}cin;
//}}}
int n,lim,m;
ll k,ans;
//{{{Matrix
struct Matrix{
ld mat[maxn][maxn];
Matrix (bool opt=0){
for (int i=0;i<=lim;++i)
for (int j=0;j<=lim;++j) mat[i][j]=opt&&(i==j);
}
ld* operator [] (const int &x){ return mat[x];}
Matrix operator * (Matrix &b){
Matrix a=*this,s;
for (int i=0;i<=lim;++i)
for (int j=0;j<=lim;++j)
for (int k=0;k<=lim;++k)
s[i][j]+=a[i][k]*b[k][j];
return s;
}
}g[maxn],s;
//}}}
inline int loc (int x,int i){ return (x-1)*3+i+1;}
inline ld sum (Matrix &a) { return a[0][0]-n; }
int main()
{
freopen("p3597.in","r",stdin);
freopen("p3597.out","w",stdout);
cin>>n>>m>>k;
lim=3*n;

for (rint i=1;i<=n;++i){
for (rint j=1;j<=2;++j) g[0][loc(i,j)][loc(i,j-1)]=1;
g[0][loc(i,0)][0]=s[0][loc(i,0)]=1;
}
g[0][0][0]=1;

for (rint i=1;i<=m;++i){
int u,v,d;
cin>>u>>v>>d;
++g[0][loc(u,0)][loc(v,d-1)];
}


int d;
for (d=1;;++d){
g[d]=g[d-1]*g[d-1];
Matrix t=s*g[d];
if (sum(t)>=k) break;
if (d>=64) return printf("-1\n"),0;
}

for (rint i=d;~i;--i){
Matrix t=s*g[i];
if (sum(t)<k){
s=t;
ans+=(1ll<<i);
}
}

printf("%lld\n",ans);
return 0;
}


如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧