看来入门组的题也能暴露出一些问题
(不要问我为什么把矩阵构造出来然后没有写矩阵加速……→_→)
# 题面
> 洛谷 P6190
点击展开/折叠题面
C 国由 $n$ 座城市与 $m$ 条有向道路组成,城市与道路都从 $1$ 开始编号,经过 $i$ 号道路需要 $t_i$ 的费用。
现在你要从 $1$ 号城市出发去 $n$ 号城市,你可以施展最多 $k$ 次魔法,使得通过下一条道路时,需要的费用变为原来的相反数,即费用从 $t_i$ 变为 $-t_i$。请你算一算,你至少要花费多少费用才能完成这次旅程。
注意:使用魔法只是改变一次的花费,而不改变一条道路自身的 $t_i$;最终的费用可以为负,并且一个城市可以经过多次(包括 $n$ 号城市)。
# 解析
根据最优性,下面这种情况不可能发生:
从一个点出发不使用魔法然后回到这个点
所以定义状态 $p_{t,i}$,表示用了 $t$ 次魔法后到达 $i$ 点的最小花费。把所有 $kn$ 个状态建点,转移建有向边,由于之前证明的结论,这是一个 DAG,更具体地说,这是一个分层图,$t$ 相同的状态在同一层。
(本来想默认大家都知道DP转移的……)点击展开/折叠递推式
设原图上两点间距离为 $d_{i,j}$,边集为 $E$,$(a,b)$ 代表 $a,b$ 两点间的边权(如果有边)。
$$p_{t,i}=\min\{p_{t-1,j}+\min_{(j,k)\in E}(d_{k,i}-(i,j))\}$$
然后我们发现对于指定的 $i,j$,上式后面那一堆 $\min$ 是可以预处理的,所以记预处理后 $\min\limits_{(j,k)\in E}(d_{k,i}-(i,j))$ 值为 $tran_{i,j}$。
这样的话,分层图中状态 $p_{t-1,j}$ 就会向 $p_{t,j}$ 连一条权为 $tran_{i,j}$ 的边。而我们发现相邻两层图之间的边的形式是完全一样的,那么能不能矩阵快速幂优化?
构造出 $n\times n$ 矩阵 $M$:$M_{i,j}=tran_{i,j}$,以及初始 $n\times 1$ 矩阵 $T^0$:$T^0_{i,1}=p_{0,i}=d_{1,i}$。然后重新定义矩阵乘法:原本矩阵乘法是一行一列对应位置相乘后相加,而我们希望能够有 $M\times T^0=T^1$($T^1_{i,1}=p_{1,i}$),则应该修改为“一行一列对应项相加后取 min”。
能否矩阵快速幂取决于是否满足交换律、分配律、结合律。
- 显然加法和 min 分别满足交换律;
- 加法满足结合律,又有 $\min\{\min\{a,b\},c\}=\min\{a,\min\{b,c\}\}$,min也满足结合律;
- 有 $\min\{a,b\}+c=\min\{a+c,b+c\}$,满足分配律(注意到这里的加法相当于“乘法”,优先级高于 min)
于是直接矩阵快速幂即可。但是注意到我们不一定直接用完 $k$ 次魔法,可能你无法用完 $k$ 次,于是不能简单地 $T^0\times(M)^k=T^k$,然后取 $p_{k,n}=T^k_{n,1}$。那么就要矩阵额外维护一行记录 $\min\limits_{t=1\to n}p_{t,n}$。
# 源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #include<queue> #include<cstdio> #include<cstring> #include<algorithm> using namespace std;
const int N=105,M=1e6+10;
int n,m,lim; long long inf; long long dis[N][N],tran[N][N],f[2][N],len[N][N];
struct MATRIX{ long long mat[N][N]; int n,m; MATRIX(int x,int y){n=x,m=y;} MATRIX(){} MATRIX operator *(const MATRIX &B){ MATRIX res(n,B.m); for(int i=1;i<=n;i++) for(int j=1;j<=B.m;j++){ long long tmp=inf; for(int k=1;k<=m;k++) if(mat[i][k]!=inf && B.mat[k][j]!=inf) tmp=min(tmp,mat[i][k]+B.mat[k][j]); res.mat[i][j]=tmp; } return res; } void PerInit(int siz){ n=m=siz; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) mat[i][j]=i==j? 0:inf; } }Tran,Res,One;
MATRIX Pow(MATRIX A,int b){ MATRIX B;B.PerInit(A.n); while(b){ if(b&1) B=B*A; A=A*A; b>>=1; } return B; }
inline int Ri(){ register int a=0,c=getchar(); while(c<'0' || '9'<c) c=getchar(); while('0'<=c && c<='9') a=(a<<1)+(a<<3)+c-'0',c=getchar(); return a; } void Dijkstra(int s){ priority_queue< pair<long long,int> > que; bool fix[N]={}; que.push(make_pair(0,s)); dis[s][s]=0; while(!que.empty()){ int u=que.top().second;que.pop(); if(fix[u]) continue; fix[u]=true; for(int v=1;v<=n;v++) if(dis[s][v]>dis[s][u]+len[u][v]) dis[s][v]=dis[s][u]+len[u][v],que.push(make_pair(-dis[s][v],v)); } } int main(){ n=Ri(),m=Ri(),lim=Ri(); memset(dis,0x3f,sizeof dis),inf=dis[0][0]; memset(len,0x3f,sizeof len); for(int i=1;i<=m;i++){ int u=Ri(),v=Ri(),l=Ri(); len[u][v]=min(len[u][v],1ll*l); } for(int i=1;i<=n;i++) Dijkstra(i); memset(tran,0x3f,sizeof tran); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++){ if(len[j][i]!=inf) tran[i][j]=-len[j][i]; for(int k=1;k<=n;k++) if(len[j][k]!=inf && dis[k][i]!=inf) tran[i][j]=min(tran[i][j],dis[k][i]-len[j][k]); } memset(f,0x3f,sizeof f); for(int i=1;i<=n;i++) f[0][i]=dis[1][i]; Tran.n=Tran.m=n+1; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) Tran.mat[i][j]=tran[i][j]; Tran.mat[i][n+1]=inf; } for(int i=1;i<n;i++) Tran.mat[n+1][i]=inf; Tran.mat[n+1][n]=Tran.mat[n+1][n+1]=0; One.n=n+1,One.m=1; for(int i=1;i<=n;i++) One.mat[i][1]=dis[1][i]; One.mat[n+1][1]=dis[1][n]; One=Pow(Tran,lim+1)*One; printf("%lld\n",One.mat[n+1][1]); return 0; }
|
THE END
Thanks for reading!