[BZOJ 1875] [SDOI 2009] HH去散步【矩阵乘法】

题目链接:BZOJ - 1875

题目分析:

  这道题如果去掉“不会立刻沿着刚刚走来的路走回”的限制,直接用邻接矩阵跑矩阵乘法就可以了。然而现在加了这个限制,建图的方式就要做一些改变。如果我们把每一条边看做点建矩阵,那么每次从一条边出发都只会到其他的边,不能仍然在这条边上“停留”,所以这就可以满足题目的限制。将每条边拆成两条单向边,比如一条编号为 4,一条编号为 5。那么 4^1=5, 5^1=4。这样只要不从第 i 条边走到 i 或 i^1 就可以了。初始的矩阵中以 A 为起点的边到达的方案数为 1 ,其余为 0。最后将终点为 B 的边的方案数累加即为答案。、

  这种将边与点灵活转化的思想十分巧妙,应注意。

代码如下:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

const int MaxN = 20 + 5, MaxM = 120 + 5, Mod = 45989;

int n, m, t, A, B, TopA, TopB, Index, RT, Ans, a, b;
int EA[MaxM], EB[MaxM];

struct Edge
{
	int u, v;
	Edge() {}
	Edge(int a, int b) {
		u = a; v = b;
	}
} E[MaxM];

struct Matrix
{
	int x, y, Num[MaxM][MaxM];
	void SetXY(int xx, int yy) {
		x = xx; y = yy;
	}
	void Clear(int nn) {
		for (int i = 0; i < x; ++i) {
			for (int j = 0; j < y; ++j) {
				Num[i][j] = nn;
			}
		}
	}
} M0, MZ;

Matrix Mul(Matrix A, Matrix B) {
	Matrix ret;
	ret.SetXY(A.x, B.y);
	ret.Clear(0);
	for (int i = 0; i < ret.x; ++i) {
		for (int j = 0; j < ret.y; ++j) {
			for (int k = 0; k < A.y; ++k) {
				ret.Num[i][j] += A.Num[i][k] * B.Num[k][j];
				ret.Num[i][j] %= Mod;
			}
		}
	}
	return ret;
}

Matrix Pow(Matrix A, int b) {
	Matrix ret, f;
	f = A;
	ret.SetXY(f.x, f.y);
	for (int i = 0; i <= ret.x; ++i) ret.Num[i][i] = 1;
	while (b) {
		if (b & 1) ret = Mul(ret, f);
		b >>= 1;
		f = Mul(f, f);
	}
	return ret;
}

int main()
{
	scanf("%d%d%d%d%d", &n, &m, &t, &A, &B);
	Index = -1;
	for (int i = 1; i <= m; ++i) {
		scanf("%d%d", &a, &b);
		E[++Index] = Edge(a, b);
		E[++Index] = Edge(b, a);
	}
	MZ.SetXY(m * 2, m * 2);
	MZ.Clear(0);
	TopA = TopB = 0;
	for (int i = 0; i <= Index; ++i) {
		if (E[i].u == A) EA[++TopA] = i;
		if (E[i].v == B) EB[++TopB] = i;
		for (int j = 0; j <= Index; ++j) {
			if (i != j && i != (j ^ 1) && E[i].v == E[j].u)
				MZ.Num[i][j] = 1;
		}
	}
	M0.SetXY(1, m * 2);
	M0.Clear(0);
	for (int i = 1; i <= TopA; ++i) M0.Num[0][EA[i]] = 1;
	MZ = Pow(MZ, t - 1);
	M0 = Mul(M0, MZ);
	Ans = 0;
	for (int i = 1; i <= TopB; ++i) {
		Ans += M0.Num[0][EB[i]];
		Ans %= Mod;
	}
	printf("%d\n", Ans);
	return 0;
}

  

时间: 2024-08-24 08:39:09

[BZOJ 1875] [SDOI 2009] HH去散步【矩阵乘法】的相关文章

BZOJ 1875 SDOI 2009 HH去散步 矩阵乘法优化DP

题目大意:给出一张无向图,求从A到B走k步(不能走回头路)的方案数.(k <= 2^30) 思路:看到k的范围就知道是矩阵乘法了.关键是不能走回头路怎么构造.正常的方法构造点的转移不能避免这个问题,就用边来构造.只要保证不经过自己^1的边就可以保证不走回头路了. CODE: #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define MAX

bzoj 1875: [SDOI2009]HH去散步 -- 矩阵乘法

1875: [SDOI2009]HH去散步 Time Limit: 20 Sec  Memory Limit: 64 MB Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但 是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回. 又因为HH是个喜欢变化的人,所以他每 天走过的路径都不完全一样,他想知道他究竟有多 少种散步的方法. 现在给你学校的地图(假设每条路的长度都 是一样的都是1),问长度为t,从给定

【bzoj1875】[SDOI2009]HH去散步 矩阵乘法

题目描述 一张N个点M条边的无向图,从A走到B,要求:每一次不能立刻沿着上一次的边的反方向返回.求方案数. 输入 第一行:五个整数N,M,t,A,B. N表示学校里的路口的个数 M表示学校里的路的条数 t表示HH想要散步的距离 A表示散步的出发点 B则表示散步的终点. 接下来M行 每行一组Ai,Bi,表示从路口Ai到路口Bi有一条路. 数据保证Ai != Bi,但不保证任意两个路口之间至多只有一条路相连接. 路口编号从0到N -1. 同一行内所有数据均由一个空格隔开,行首行尾没有多余空格.没有多

[SDOI 2009] HH去散步

[题目链接] https://www.lydsy.com/JudgeOnline/problem.php?id=1875 [算法] 用f[i][j]表示现在在走了i步 , 在第j条边的方案数 矩阵加速 , 即可 时间复杂度 : O(N ^ 3logN) [代码] #include<bits/stdc++.h> using namespace std; #define MAXN 125 const int P = 45989; struct edge { int to , nxt; } e[MA

BZOj-1875: [SDOI2009]HH去散步 (矩阵快速幂)

1875: [SDOI2009]HH去散步 Time Limit: 20 Sec  Memory Limit: 64 MBSubmit: 1999  Solved: 980[Submit][Status][Discuss] Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但 是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回. 又因为HH是个喜欢变化的人,所以他每 天走过的路径都不完全一样,他想知道他究竟

bzoj1875 [SDOI2009]HH去散步 矩阵快速幂

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=1875 题解 如果没有这个"不能立刻沿着刚刚走来的路走回",那么这个题就是一个常规的矩阵乘法. 考虑一下这个限制怎么解决.因为限制的是边,我们不妨考虑和边有关的矩阵. 首先把一条无向边拆成两个有向边,如果边 \(A\) 的终点和边 \(B\) 的起点相同,那么我们就说从边 \(A\) 通向边 \(B\).但是,同源的有向边(也就是从同一条无向边拆成的两条有向边)之间不能建边. 但是为

BZOJ 1878 SDOI 2009 HH项链 树状数组 + 脱机处理

标题效果:一些珠子项链.珠具有不同的颜色.我们问了很多次有多少种不同的颜色有过一段范围. 思考:这个问题让我学会聪明的离线实践.按左端点排序问题.加工出来的位置每种颜色首次出现.每一种颜色的下一次出现的位置.然后,1至cnt周期,这里有一个问题的左端点是当前节点,就处理他的答案.方法是前缀合,能够用树状数组.然后把这个颜色的下一个出现的位置+1. 这样做就避免了一种颜色在询问中被处理两次. CODE: #include <cstdio> #include <cstring> #in

BZOJ 1878 SDOI 2009 HH的项链 树状数组 + 离线处理

题目大意:有一些珠子串成的项链,珠子有不同的颜色.多次询问一段区间内有多少不同的颜色. 思路:这个题让我学会了一种巧妙的离线做法.将问题按左端点排序.处理出来每个颜色第一个出现的位置,和每个颜色下一个出现的位置.然后1到cnt循环,如果这里有一个问题的左端点是当前节点,就处理他的答案,方法是前缀合,可以用树状数组.然后把这个颜色的下一个出现的位置+1. 这样做就避免了一种颜色在询问中被处理两次. CODE: #include <cstdio> #include <cstring>

BZOJ 1875: [SDOI2009]HH去散步( dp + 矩阵快速幂 )

把双向边拆成2条单向边, 用边来转移...然后矩阵乘法+快速幂优化 --------------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MOD = 45989; const int