YZOJ P3527 [FJOI2018D1T3]城市路径问题
时间限制:1000MS 内存限制:131072KB
难度:\(6.5\)
-
题目描述
给出一张 \(n\) 个点的有向图 \(G(V, E)\) 。对于任意两个点 \(u, v\) (\(u\) 可以等于 \(v\) ),\(u\) 向 \(v\) 的连边数为:\(\sum\limits_{i=1}^k {out[u, i] \times in[v, i]}\) 。
给定 \(k\) 和数组 \(out, in\) ,现在有 \(m\) 个询问,每次询问给出三个参数 \(u, v, d\),你需要回答从节点 \(u\) 出发,经过不超过 \(d\) 条边到达节点 \(v\) 的路径有多少种。
答案对 \(10^9+7\) 取模。
-
输入格式
第一行两个整数 \(n, k\) 。
接下来 \(n\) 行,第 \(i\) 行有 \(2k\) 个整数,前 \(k\) 个整数描述 \(out[i][]\),后 \(k\) 个数描述 \(in[i][]\) 。
接下来一行一个整数 \(m\) 。
接下来 \(m\) 行,每行三个整数 \(u, v, d\),描述一组询问。
-
输出格式
对于每个询问,输出一个方案数。由于答案可能太大,输出其除以 \(10^9+7\) 后的余数。
-
样例输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
5 2 2 5 4 3 7 9 2 4 0 1 5 2 6 3 9 2 2147483647 1000000001 233522 788488 10 1 1 0 2 2 1 2 4 5 4 3 10 3 4 50 1 5 1000 3 5 1000000000 1 2 500000000 4 5 2147483647 3 1 2147483647 |
-
样例输出
1 2 3 4 5 6 7 8 9 10 |
1 51 170107227 271772358 34562176 890241289 8516097 383966304 432287042 326522835 |
-
数据规模与约定
对于 \(40\%\) 的数据,\(n \leq 50 ,k \leq 20, m \leq 45\) 。
对于 \(90\%\) 的数据,\(n \leq 1000, k \leq 20, m \leq 50\) 。
对于另外 \(10\%\) 的数据,\(n \leq 1000, k=1, m \leq 100\) 。
所有输入数据不超过 int 范围。
Source: BZOJ 3583 (不是 FJOI 吗?www
首先,若矩阵 \(A\) 中 \((s, t)\) 的值表示从 \(s\) 到 \(t\) 的路径条数,那么 \(A^d\) 表示从 \(s\) 出发刚好经过 \(d\) 条路到达 \(t\) 的方案数。
可以考虑矩阵乘法的过程,\((i, j) = (i, k) \times (k, j)\) ,其实就是枚举经过点 \(k\) 把方案数乘起来,显然可以得到这个结论。
那么题目说的 “不超过 \(d\) 条” 的意思就是 \(A^0+A^1+A^2+ \cdots + A^d\) ,暴力算一下就会有 \(40pts\) 。
\(s \to t\) 的路径条数为 \(\sum\limits_{i=1}^k {out[s, i] \times in[t, i]}\) ,也可以看成是一个矩阵乘法的过程,即 \(OI^T\) 。
那么要求的是 \(G^d=\left(OI^T\right)^d\) ,若使用矩阵快速幂的话时间复杂度为 \(O(n^2k \log d)\),因为计算一次 \(OI^T\) (\(O\) 为 \(n \times k\),\(I^T\) 为 \(k \times n\))需要 \(O(n^2k)\) 的复杂度,很明显无法通过本题(毕竟是 FJOI 的机子)。
考虑优化,根据矩阵乘法的结合律,有
\(G^d=\left(OI^T\right)^d = \underbrace{OI^T \times OI^T \times \cdots \times OI^T}_d \\ =O \times \underbrace{I^TO \times I^TO \times \cdots \times I^TO}_{d-1} \times I^T = O\left(I^TO\right)^{d-1}I^T\)
因为 \(I^T\) 为 \(k \times n\),\(O\) 为 \(n \times k\),所以计算一次 \(I^TO\) 的复杂度只是 \(O(nk^2)\),而本题中 \(k\) 很小,可以接受。
另外,矩阵幂和没有什么 \(A^0+A^1+\cdots+A^d = \frac{A^{d+1}-1}{x-1}\) (不可能有好吧www),要想达到与快速幂同阶的复杂度,需要通过别的方法计算。
注意到
\(A^0+A^1+A^2+ \cdots A^{2n} = \left(A^0+A^1+\cdots+A^n\right) + A^n \left(A^0+A^1+\cdots+A^n\right)\)
\(A^0+A^1+A^2+ \cdots A^{2n+1} = \left(A^0+A^1+\cdots+A^n\right) + A^n \left(A^0+A^1+\cdots+A^n+A^{n+1}\right)\)
所以可以通过分治法在 \(O(\log d)\) 的复杂度内完成。
最后再对于每个询问的 \(s, t\) 乘一遍即可,时间复杂度 \(O\left(nk^2+mk^3\log d\right)\) 。
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 |
#include <cstdio> #include <cstdlib> #include <cstring> #include <climits> #define MOD 1000000007 inline int getnum() { register char c=0; while(!(c>='0' && c<='9')) c=getchar(); register int a=0; while(c>='0' && c<='9') a=a*10+c-'0',c=getchar(); return a; } int pK; struct _mat { int a[20][20]; _mat(){memset(a,0,sizeof(a));} _mat operator * (const _mat&o)const { _mat ans; for(register int i=0;i<pK;i++) for(register int j=0;j<pK;j++) for(register int k=0;k<pK;k++) (ans.a[i][j]+=(long long)this->a[i][k]*o.a[k][j]%MOD)%=MOD; return ans; } _mat operator + (const _mat&o)const { _mat ans; for(register int i=0;i<pK;i++) for(register int j=0;j<pK;j++) ans.a[i][j]=(this->a[i][j]+o.a[i][j])%MOD; return ans; } }I; int vin[1050][20],vout[1050][20]; _mat base; void PowerSum(_mat&A,_mat&B,register int d) { if(d==1) { A=B=base; return; } PowerSum(A,B,d>>1); B=B+B*A,A=A*A; if(d&1) A=base*A,B=B+A; } int main() { register int N=getnum(),K=getnum(); pK=K; for(register int i=0;i<K;i++) I.a[i][i]=1; for(register int i=0;i<N;i++) { for(register int j=0;j<K;j++) vout[i][j]=getnum()%MOD; for(register int j=0;j<K;j++) vin[i][j]=getnum()%MOD; } for(register int i=0;i<K;i++) for(register int j=0;j<K;j++) for(register int k=0;k<N;k++) (base.a[i][j]+=(long long)vin[k][i]*vout[k][j]%MOD)%=MOD; register int M=getnum(); for(register int lp=1;lp<=M;lp++) { register int u=getnum()-1,v=getnum()-1,d=getnum(); register int ans=(u==v); if(!d) { printf("%d\n",ans); continue; } _mat ansum,ad; if(d>1) PowerSum(ad,ansum,d-1); ansum=ansum+I; for(register int j=0;j<K;j++) for(register int k=0;k<K;k++) (ans+=(long long)vout[u][j]*ansum.a[j][k]%MOD*vin[v][k]%MOD)%=MOD; printf("%d\n",ans); } return 0; } |