King Bombee(图上dp)
题意
给定一个\(n\)个点\(m\)边的无向简单图。现在给你\(4\)个整数\(S\),\(T\),\(K\),\(X\)。请你求出有多少条从\(S\)到\(T\)的路径满足如下条件:
- 途中经过\(K\)条边
- 经过点\(X\)的次数为偶数(可能为\(0\))
数据范围
\(2 \leq n \leq 2000\)
\(1 \leq m \leq 2000\)
\(1 \leq K \leq 2000\)
思路
首先不管第二个条件,只考虑第一个条件。我们考虑使用DP。令\(f(i,j)\)表示起点是\(S\),终点是\(j\),经过\(i\)条边的路径总数,则:
- \(f(0, S) = 1\),\(f(0, j) = 0(j \neq S)\)
- \(f(i, j) = \sum_\limits{k \in adj(j)} f(i - 1, k)\)
下面说明一下,为什么这里可以使用DP,而不用考虑后效性问题。
我们可以将图拓展一下,把原先的图复制\(K\)份。然后对这\(K + 1\)个图从\(0\)开始标号,我们将这个标号称为“层”。
转移数组\(f(i, j)\)表示的是从第\(0\)层的\(S\)点到第\(i\)层的\(j\)点的方案数。在进行转移的时候,到达当前层的方案数只能从上一层进行转移,不会从同一层的点转移(简单图)。
因此,在这里使用DP不存在后效性问题。
现在,把第二个条件考虑在内,在DP中说到奇偶问题,一个常用的技巧就是将奇数看作是“\(1\)”状态,将偶数看作是“\(0\)”状态。那么我们改进一下我们的转移数组。
令\(f(i, j, 0/1)\)表示起点是\(S\),终点是\(j\),经过\(i\)条边且经过\(X\)点次数为偶数/奇数的路径总数,则:
- \(f(0, S, 0) = 1\)
- \(f(i, j, t) = \sum_\limits{k \in adj(j)} f(i - 1, k, t)(j \ne X)\)
- \(f(i, X, t) = \sum_\limits{k \in adj(X)} f(i - 1, k, 1 - t)\)
最终答案即为:\(f(K, T, 0)\)
代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N = 2010, M = 2 * N, mod = 998244353;
int n, m, K;
int S, T, X;
int h[N], e[M], ne[M], idx;
ll f[N][N][5];
void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}
int main()
{
scanf("%d%d%d%d%d%d", &n, &m, &K, &S, &T, &X);
memset(h, -1, sizeof h);
for(int i = 0; i < m; i ++) {
int a, b;
scanf("%d%d", &a, &b);
add(a, b), add(b, a);
}
f[0][S][0] = 1;
for(int i = 1; i <= K; i ++) {
for(int j = 1; j <= n; j ++) {
for(int k = 0; k < 2; k ++) {
if(j == X) {
for(int l = h[j]; ~l; l = ne[l]) {
int v = e[l];
f[i][j][k] = (f[i][j][k] + f[i - 1][v][1 - k]) % mod;
}
}
else {
for(int l = h[j]; ~l; l = ne[l]) {
int v = e[l];
f[i][j][k] = (f[i][j][k] + f[i - 1][v][k]) % mod;
}
}
}
}
}
printf("%lld\n", f[K][T][0]);
return 0;
}