HH去散步
题目限制
- 内存限制:125.00MB
- 时间限制:1.00s
- 标准输入
- 标准输出
题目知识点
- 动态规划 d p dp dp
- 矩阵
- 矩阵乘法
- 矩阵加速
- 矩阵快速幂
- 思维
- 构造
题目来源
题目
题目背景
HH 有个一成不变的习惯,喜欢在饭后散步,就是在一定的时间内,走一定的距离
同时, HH 是一个喜欢变化的人,她不会立刻沿着刚刚走过来的路走回去,她也希望每天走过的路径都不完全一样,她想知道每一天他究竟有多少种散步的方法
题目描述
现在 HH 送给你一张学校的地图,请你帮助她求出从地点 A A A 走到地点 B B B 一共有多少条长度为 T T T 的散步路径(答案对 45989 45989 45989 取模)
格式
输入格式
输入共
M
+
1
M + 1
M+1 行:
第
1
1
1 行:输入
5
5
5 个整数
N
,
M
,
T
,
A
,
B
N, \ M, \ T, \ A, \ B
N, M, T, A, B;
N
N
N 表示 学校里的路口的个数(编号为
0
∼
N
−
1
0 \sim N - 1
0∼N−1),
M
M
M 表示 学校里的道路的条数,
T
T
T 表示 HH 想要散步的距离,
A
A
A 表示 散步的出发点,
B
B
B 表示 散步的终点
接下来
M
M
M 行:每行
2
2
2 个用空格隔开的整数
u
i
,
v
i
u_i, \ v_i
ui, vi;表示 长度为
1
1
1 的第
i
i
i 条路 连接 路口
u
i
u_i
ui 和 路口
v
i
v_i
vi
输出格式
输出共一行:表示你所求出的答案(对 45989 45989 45989 取模)
样例
样例输入
4 5 3 0 0
0 1
0 2
0 3
2 1
3 2
样例输出
4
提示
数据范围
对于
30
%
30 \%
30% 的数据:满足
N
≤
4
,
M
≤
10
,
T
≤
10
N \leq 4, \ M \leq 10, \ T \leq 10
N≤4, M≤10, T≤10
对于
100
%
100 \%
100% 的数据:满足
N
≤
50
,
M
≤
60
,
T
≤
2
30
,
u
i
≠
v
i
N \leq 50, \ M \leq 60, \ T \leq 2 ^ {30}, \ u_i \neq v_i
N≤50, M≤60, T≤230, ui=vi
思路
这道题如果没有 她不会立刻沿着刚刚走过来的路走回去 的限制,就可以根据点与点的关系先构造出一个
n
∗
n
n * n
n∗n 的矩阵
x
\mathrm{x}
x(
x
[
i
]
[
j
]
\mathrm{x}[i][j]
x[i][j] 表示从
i
i
i 走
1
1
1 步到
j
j
j 的方案数),累乘
T
T
T 次(就是走了
T
T
T 步),就用矩阵快速幂优化既可以通过了
现在就考虑加上这句话的限制后如何构造矩阵了
分析
考虑矩阵定义大致不变,即
x
[
i
]
[
j
]
\mathrm{x}[i][j]
x[i][j] 表示从
i
i
i 走
1
1
1 步到
j
j
j 的方案数
由于有限制,就要记录刚刚走过来的路是哪一条
不妨把每条边对应的
u
i
u_i
ui 和
v
i
v_i
vi 拆成两个二元组
(
n
o
d
e
,
i
d
)
\mathrm{(node, id)}
(node,id),表示刚刚从第
i
d
\mathrm{id}
id 条路走到
n
o
d
e
\mathrm{node}
node,也就是每条无向边
(
u
i
↔
,
v
i
)
(u_i \leftrightarrow, v_i)
(ui↔,vi) 分成两条有向边
(
u
i
→
v
i
)
(u_i \to v_i)
(ui→vi) 和
(
v
i
→
u
i
)
(v_i \to u_i)
(vi→ui),其中
n
o
d
e
\mathrm{node}
node 表示当前这条有向边的终点,
i
d
\mathrm{id}
id 表示与之对应的无向边的编号
那么
x
[
i
]
[
j
]
=
1
\mathrm{x}[i][j] = 1
x[i][j]=1 定义就是 第
i
i
i 个二元组 走
1
1
1 步到 第
j
j
j 个二元组 的方案数
其值只可能为
0
0
0 或
1
1
1(因为只走了
1
1
1 步),其中值为
1
1
1 的条件就是
i
d
i
≠
i
d
j
\mathrm{id}_i \neq \mathrm{id}_j
idi=idj 且
n
o
d
e
i
\mathrm{node}_i
nodei 与
n
o
d
e
j
\mathrm{node}_j
nodej 有一条边
推出了矩阵,但是还有一个细节,就是第一步的方案数
起始点是没有上一条边的,所以需要预处理一下(这里相当于先走了一次)
预处理矩阵
×
\times
× 矩阵快速幂(
T
−
1
T - 1
T−1 次,预处理走了一次)就可以得到最终的矩阵了
最后把 起始点(超级源点) 到 终点(可能有多个,因为分了边) 的路径加起来取模就可以了
代码
#include <cstdio>
#include <cstring>
int rint()
{
int x = 0, fx = 1; char c = getchar();
while (c < '0' || c > '9') { fx ^= ((c == '-') ? 1 : 0); c = getchar(); }
while ('0' <= c && c <= '9') { x = (x << 3) + (x << 1) + (c ^ 48); c = getchar(); }
if (!fx) return -x;
return x;
}
const int MOD = 45989;
const int MAX_N = 20;
const int MAX_M = 60;
int N, M, T, A, B, node;
int e[MAX_M * 2 + 5][3];
struct Matrix
{
int mx[MAX_M * 2 + 5][MAX_M * 2 + 5];
Matrix () { memset(mx, 0, sizeof(mx)); }
void init() { for (int i = 0; i <= node; i++) mx[i][i] = 1; }
Matrix operator * (const Matrix &rhs) const
{
Matrix res;
for (int i = 0; i <= node; i++)
for (int j = 0; j <= node; j++)
for (int k = 0; k <= node; k++)
res.mx[i][j] = (res.mx[i][j] + mx[i][k] * rhs.mx[k][j]) % MOD;
return res;
}
} dp, quick;
Matrix qpow(Matrix mx, int k)
{
Matrix res; res.init();
while (k > 0)
{
if (k & 1) res = res * mx;
mx = mx * mx; k >>= 1;
}
return res;
}
int main()
{
N = rint(), M = rint(), T = rint();
A = rint() + 1, B = rint() + 1;
for (int i = 1; i <= M; i++)
{
e[i][0] = rint() + 1, e[i][1] = rint() + 1;
e[i + M][0] = e[i][1], e[i + M][1] = e[i][0];
if (e[i][0] == A) ++dp.mx[0][i];
if (e[i + M][0] == A) ++dp.mx[0][i + M];
}
node = M << 1;
for (int i = 1; i <= node; i++)
for (int j = 1; j <= node; j++)
if (i + M != j && i - M != j && e[i][1] == e[j][0]) ++quick.mx[i][j];
int ans = 0;
Matrix res = dp * qpow(quick, T - 1);
for (int i = 1; i <= node; i++)
if (e[i][1] == B) ans = (ans + res.mx[0][i]) % MOD;
printf("%d\n", ans);
return 0;
}