[usaco2007 Nov]relays 奶牛接力跑 题解 重载矩阵乘法
题目描述
输入格式
输出格式
样例
思路:首先这个题的想法要和SCOI2009迷路的题类似,我并不倾向于把这题叫做倍增floyd,我认为这题从矩阵的角度更好理解,而且更能证明正确性,显然从迷路的思路我们得到这么几个条件,一个是矩阵存的路径数,一个是走几步。那么类比一下,我们当时要求k次的路径数,这时我们要求k条边的最短路,那么很容易我们想到了,矩阵里面存边权,代表i到j走一条边的最短路,那么我们可以yy一下,这个矩阵的k次幂就是i到j走k条边的最短路,那么显然之前矩阵的乘法是不能计算的,于是我们就想到了把矩阵乘法重载一下,变为c[i][j]=min(a[i][k]+a[k][j]),这也就是网上许多倍增floyd的由来,但是接下来要问了,floyd可以倍增么?换个问题就是这样做对吗?那么我们要从这种思路来看,我们没有证明我们重载过的矩阵运算满足交换律,即(a@a)@a==a@(a@a);那么这应该是这个算法的正确性,我们考虑实际含义,走2步再走1步,和走1步再走2步,最短路一定相同。当然并没有说倍增floyd错了,代码打出来都一样,以上均为个人见解。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int N=105,ID=1000,inf=0x7fffffff; int p[ID],m[N][N],l[N],n,k,s,e,cnt; struct mat { long long a[105][105],n; mat (int n,int x) :n(n) { for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) a[i][j]=x; } mat operator *(mat &b) { mat c(n,inf); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) c.a[i][j]=min(c.a[i][j],a[i][k]+b.a[k][j]); return c; } void print() { for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) if(a[i][j]==inf) printf("# "); else cout<<a[i][j]<<" "; cout<<endl; } } }; int rd() { char cc=getchar(); int s=0,w=1; while(cc<'0'||cc>'9') {if(cc=='-') w=-1;cc=getchar();} while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar(); return s*w; } mat qpow(mat a,int k) { mat ans=a; for(;k;k>>=1,a=a*a)if(k&1) ans=ans*a; return ans; } int main() { k=rd(),n=rd(),s=rd(),e=rd(); for(int i=1,z,x,y;i<=n;i++) { z=rd(),x=rd(),y=rd(); if(!p[x]) p[x]=++cnt; if(!p[y]) p[y]=++cnt; //printf("%d %d\n",p[x],p[y]); m[p[x]][p[y]]=z; m[p[y]][p[x]]=z; } mat a(cnt,0); for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++) a.a[i][j]=m[i][j]?m[i][j]:inf; //a.print(); mat c=qpow(a,k-1); //c.print(); printf("%lld\n",c.a[p[s]][p[e]]); } /* g++ 1.cpp -o 1 ./1 2 6 6 4 11 4 6 4 4 8 8 4 9 6 6 8 2 6 9 3 8 9 */
Zeit und Raum trennen dich und mich.时空将你我分开。