改题链接
C. 不走回头路
我们先考虑暴力 dp
。
我们可以记 \(dp_{i, j, k}\) 表示当前走了 \(i\) 条边,在第 \(j\) 个点,上一次走的是编号为 \(k\) 的边。
那么我们可以写出转移:
dp[0][1][0] = sum[0][1] = 1;
for (int i = 1; i <= k; i++) {for (int j = 1; j <= n; j++) {for (auto [t, id] : g[j]) {dp[i & 1][j][id] = (sum[i - 1][t] + mod - dp[(i & 1) ^ 1][t][id]) % mod;// 这里需要滚动,为了方便,还是描述为 dp[i][j][id](sum[i][j] += dp[i & 1][j][id]) %= mod;}}
}
cout << sum[k][n];
然后,我们可以列出式子:
\[dp_{i, j, id} = sum_{i - 1, t} - dp_{i - 1, t, id}
\]
\[dp_{i - 1, t, id} = sum_{i - 2, j} - dp_{i - 2, j, id}
\]
\[dp_{i - 2, j, id} = sum_{i - 3, t} - dp_{i - 3, t, id}
\]
\[\vdots
\]
然后,我们将式子分别代入进去:
\[dp_{i, j, id} = sum_{i - 1, t} - sum_{i - 2, j} + sum_{i - 3, t} - sum_{i - 4, j} + \dots
\]
像这样,我们会发现这个 \(dp\) 数组毫无用处,可以直接变成用 \(sum\) 数组计算答案。
然后就可以写出这样的暴力:
sum[0][1] = 1;
for (int i = 1; i <= k; i++) {for (int j = 1; j <= n; j++) {for (auto [t, id] : g[j]) {(sum[i][j] += sum[i - 1][t] - sum[i - 2][j] + mod) %= mod;}if (i > 1) (sum[i][j] += sum[i - 2][j]) %= mod;}
}
cout << (sum[k][n] - sum[k - 2][n] + mod) % mod;
我们再把这个 \(sum_{i - 2, j}\) 合并一下。
sum[0][1] = 1;
for (int i = 1; i <= k; i++) {for (int j = 1; j <= n; j++) {for (auto [t, id] : g[j]) {(sum[i][j] += sum[i - 1][t]) %= mod;}if (i > 1) (sum[i][j] += (1 + mod - g[j].size()) % mod * sum[i - 2][j] % mod) %= mod;}
}
cout << (sum[k][n] - sum[k - 2][n] + mod) % mod;
然后我们就发现这个 \(sum\) 的转移非常简单,状态也非常简单,一看就可以用矩阵加速。
这个比较像斐波那契数列的递推式。
我们可以列出