「SDOI2009」HH去散步

文章描述了一个关于路径计数的问题,HH在校园里从A点到B点散步,希望每天的路径不重复,长度为T。给定地图信息和路径限制,需要计算不同的散步路径数量。解决方案涉及到动态规划和矩阵快速幂算法,通过构建特定的矩阵并进行快速幂运算来找出答案。
摘要由CSDN通过智能技术生成

HH去散步

题目限制

  • 内存限制:125.00MB
  • 时间限制:1.00s
  • 标准输入
  • 标准输出

题目知识点

  • 动态规划 d p dp dp
  • 矩阵
    • 矩阵乘法
    • 矩阵加速
    • 矩阵快速幂
  • 思维
    • 构造

题目来源

「SDOI2009」HH去散步

题目

题目背景

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 0N1 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 N4, M10, T10
对于 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 N50, M60, T230, ui=vi


思路

这道题如果没有 她不会立刻沿着刚刚走过来的路走回去 的限制,就可以根据点与点的关系先构造出一个 n ∗ n n * n nn 的矩阵 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) (uivi) ( v i → u i ) (v_i \to u_i) (viui),其中 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 T1 次,预处理走了一次)就可以得到最终的矩阵了
最后把 起始点(超级源点)终点(可能有多个,因为分了边) 的路径加起来取模就可以了


代码

#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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值