[SCOI2009] 迷路(dp+矩阵快速幂)

题目大意

给你一张图,你刚开始在1号节点,每次你可以走到相邻的节点,每条边有一个边权,代表需要花费的时间。问有多少种方案刚好t时间走到n号节点。答案对2009取模。

对于 \(30\%\) 的数据,保证 \(n \leq 5\)\(t \leq 30\)

对于 \(100\%\) 的数据,保证 \(2 \leq n \leq 10\)\(1 \leq t \leq 10^9\)

Analysis

先想一想\(30\%\)的数据怎么做?看到计数问题多半是组合数学或dp。而这题组合数学应该不太好做所以我们考虑dp。

设经过\(i\)步到达节点\(u\)的方案数为\(dp[i][u]\)。那所有连向u的边都可以被用来扩展,所以\(dp[i][u]=\sum_{(v,u) \in E} dp[i-val(v,u)][v]\)\(val(v,u)\)是边权。

这样做应该是\(O(n^2t)\)的(菊花图)。

怎么优化呢?如果边权只有可能是1的话,就有\(dp[i][u]=\sum_{(v,u) \in E} dp[i-1][v]\)。i的dp方程只与i-1有关,所以可以矩阵快速幂优化。

注意到边权只有可能是1-9,那么我们也可以用一个稍微大一点的矩阵来存。

具体来讲假设我们现在要求的是m时刻。

那我们的答案向量可以是\(\left[ \begin{matrix} dp[m-1][1] & dp[m-1][2] & ... & dp[m-1][n] & ... & dp[m-9][1] & ... & dp[m-9][n] \end{matrix} \right]\)

如果存在一条\(v->u\)的边且边权为\(val(v,u)\)。则令转移矩阵的第\(n \times (val(v,u)-1)+v\)行第\(u\)列设为1即可。

而为了保存m-1到m-8作为下一步转移的需要,我们还要做一步预处理。

Code

//迷路 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct Matrix{
	int arr[100][100];
	int size;
}g, dp;
int n, t;
char s[11];
int sy[11][11];
inline void init(Matrix &ret, int size) {
	ret.size = size;
	for (int i = 1; i <= size; ++i) {
		for (int j = 1; j <= size; ++j) {
			ret.arr[i][j] = 0;
		}
	}
}
inline Matrix Mul(Matrix A, Matrix B) {
	Matrix C;
	init(C, A.size);
	for (int i = 1; i <= A.size; ++i) {
		for (int k = 1; k <= A.size; ++k) {
			if (A.arr[i][k] == 0) continue;
			for (int j = 1; j <= A.size; ++j) {
				C.arr[i][j] = (C.arr[i][j] + A.arr[i][k] * B.arr[k][j]) % 2009;
			}
		}
	}
	return C;
}
inline Matrix ksm(Matrix A, int b) {
	Matrix ret;
	init(ret, A.size);
	for (int i = 1; i <= ret.size; ++i) ret.arr[i][i] = 1;
	while (b) {
		if (b & 1) ret = Mul(ret, A);
		A = Mul(A, A);
		b >>= 1;
	}
	return ret;
}
int main() {
	scanf("%d%d", &n, &t);
	init(g, n * 9);
	for (int i = 1; i <= n; i++) {
		scanf("%s", s + 1);
		for (int j = 1; j <= n; j++) {
			int k = s[j] - '0';
			sy[i][j] = k; 
			if (k) {
				g.arr[(9 - k) * n + i][n * 8 + j] = 1;
			}
		}
	}
	for (int i = n * 9; i > n; i--) {
		g.arr[i][i - n] = 1;
	}
	init(dp, n * 9);
	dp.arr[1][1] = 1;
	for (int i = 1; i <= min(t, 8); i++) {
		for (int j = 1; j <= n; j++) {
			for (int k = 1; k <= n; k++) {
				if (sy[j][k] && i >= sy[j][k]) {
					dp.arr[1][i * n + k] += dp.arr[1][(i - sy[j][k]) * n + j];
				}
			}
		}
	}
	if (t <= 8) {
		printf("%d", dp.arr[1][t * n + n]);
	} else {
		dp = Mul(dp, ksm(g, t - 8));
		printf("%d", dp.arr[1][n * 9]);
	}
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/zcr-blog/p/13191548.html