「 洛谷 」P2151 [SDOI2009]HH去散步

Rabbit_Mua發表於2021-06-05

小兔的話

歡迎大家在評論區留言哦~


HH去散步

題目限制

  • 記憶體限制:125.00MB
  • 時間限制:1.00s
  • 標準輸入
  • 標準輸出

題目知識點

  • 動態規劃 \(dp\)
  • 矩陣
    • 矩陣乘法
    • 矩陣加速
    • 矩陣快速冪
  • 思維
    • 構造

題目來源

「 洛谷 」P2151 [SDOI2009]HH去散步


為了方便大家閱讀通暢,題目可能略有改動,保證不會造成影響

題目

題目背景

HH 有個一成不變的習慣,喜歡在飯後散步,就是在一定的時間內,走一定的距離
同時, HH 是一個喜歡變化的人,她不會立刻沿著剛剛走過來的路走回去,她也希望每天走過的路徑都不完全一樣,她想知道每一天他究竟有多少種散步的方法

題目描述

現在 HH 送給你一張學校的地圖,請你幫助她求出從地點 \(A\) 走到地點 \(B\) 一共有多少條長度為 \(T\) 的散步路徑(答案對 \(45989\) 取模)

格式

輸入格式

輸入共 \(M + 1\) 行:
\(1\) 行:輸入 \(5\) 個整數 \(N, \ M, \ T, \ A, \ B\)\(N\) 表示 學校裡的路口的個數(編號為 \(0 \sim N - 1\)\(M\) 表示 學校裡的道路的條數\(T\) 表示 HH 想要散步的距離\(A\) 表示 散步的出發點\(B\) 表示 散步的終點
接下來 \(M\) 行:每行 \(2\) 個用空格隔開的整數 \(u_i, \ v_i\);表示 長度為 \(1\) 的第 \(i\) 條路 連線 路口 \(u_i\)路口 \(v_i\)

輸出格式

輸出共一行:表示你所求出的答案(對 \(45989\) 取模)

樣例

樣例輸入

4 5 3 0 0
0 1
0 2
0 3
2 1
3 2

樣例輸出

4

提示

資料範圍

對於 \(30 \%\) 的資料:滿足 \(N \leq 4, \ M \leq 10, \ T \leq 10\)
對於 \(100 \%\) 的資料:滿足 \(N \leq 50, \ M \leq 60, \ T \leq 2 ^ {30}, \ u_i \neq v_i\)


思路

這道題如果沒有 她不會立刻沿著剛剛走過來的路走回去 的限制,就可以根據點與點的關係先構造出一個 \(n * n\) 的矩陣 \(\mathrm{x}\)\(\mathrm{x}[i][j]\) 表示從 \(i\)\(1\) 步到 \(j\) 的方案數),累乘 \(T\) 次(就是走了 \(T\) 步),就用矩陣快速冪優化既可以通過了
現在就考慮加上這句話的限制後如何構造矩陣了


分析

考慮矩陣定義大致不變,即 \(\mathrm{x}[i][j]\) 表示從 \(i\)\(1\) 步到 \(j\) 的方案數
由於有限制,就要記錄剛剛走過來的路是哪一條
不妨把每條邊對應的 \(u_i\)\(v_i\) 拆成兩個二元組 \(\mathrm{(node, id)}\),表示剛剛從第 \(\mathrm{id}\) 條路走到 \(\mathrm{node}\),也就是每條無向邊 \((u_i \leftrightarrow, v_i)\) 分成兩條有向邊 \((u_i \to v_i)\)\((v_i \to u_i)\),其中 \(\mathrm{node}\) 表示當前這條有向邊的終點,\(\mathrm{id}\) 表示與之對應的無向邊的編號
那麼 \(\mathrm{x}[i][j] = 1\) 定義就是 \(i\) 個二元組\(1\) 步到 \(j\) 個二元組 的方案數
其值只可能為 \(0\)\(1\)(因為只走了 \(1\) 步),其中值為 \(1\) 的條件就是 \(\mathrm{id}_i \neq \mathrm{id}_j\)\(\mathrm{node}_i\)\(\mathrm{node}_j\) 有一條邊
推出了矩陣,但是還有一個細節,就是第一步的方案數
起始點是沒有上一條邊的,所以需要預處理一下(這裡相當於先走了一次)
預處理矩陣 \(\times\) 矩陣快速冪(\(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;
}