*题解:P2151 [SDOI2009] HH 去散步
解析
考虑没有不能立刻往回走的限制怎么做。
设 \(f_{i,j}\) 表示长度为 \(i\) 的从 \(A\) 到 \(j\) 的路径条数。则有:
\[f_{i,j} = \sum_{k=1}^{N} f_{i - 1,k} \cdot e_{k,j}
\]
其中 \(e_{k,j}\) 表示连接 \(k\) 和 \(j\) 的边数。
\(t\) 很大,矩阵加速即可。
对于限制,我们有个 trick 叫点边互化。既然对边有限制,那就把边放到状态里。把无向边拆成两条有向边,重新设 \(f_{i,j}\) 表示长度为 \(i\) 的从 \(A\) 经过边 \(j\) 走到边 \(j\) 终点的方案数。则有:
\[f_{i,j} = \sum_{k\in S_j}f_{i - 1,k}
\]
其中 \(S_j\) 为终点为边 \(j\) 起点的边的编号组成的集合。
同样,矩阵加速即可。
时间复杂度 \(O(M^3 \log t)\)。
代码
我的实现需要从虚空中拉一条边指向 \(A\),用于初始矩阵。
/*
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N = 50 + 5,M = 60 + 5,P = 2000000,mod = 45989;
vector<int> to[N];
int u[M * 2],v[M * 2];
struct Mtx{
int row,col;
int m[M * 2][M * 2];
Mtx(int r,int c) : row(r),col(c) {
for(int i=1;i<=row;i++){
for(int j=1;j<=col;j++){
m[i][j] = 0;
}
}
}
void I(){
for(int i=1;i<=row;i++){
m[i][i] = 1;
}
}
friend Mtx operator * (Mtx a,Mtx b){
Mtx c(a.row,b.col);
for(int i=1;i<=a.row;i++){
for(int j=1;j<=b.col;j++){
for(int k=1;k<=a.col;k++){
c.m[i][j] = (c.m[i][j] + a.m[i][k] * b.m[k][j]) % mod;
}
}
}
return c;
}
void print(){
for(int i=1;i<=row;i++){
for(int j=1;j<=col;j++){
cout<<m[i][j]<<" \n"[j==col];
}
}
cout<<"_______________________________\n";
}
};
Mtx qmi(Mtx a,int b){
Mtx res(a.row,a.col);
res.I();
while(b){
if(b & 1) res = res * a;
b >>= 1;
a = a * a;
}
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n,m,t,A,B;
cin>>n>>m>>t>>A>>B;
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i];
u[m + i] = v[i];
v[m + i] = u[i];
to[v[i]].push_back(i);
to[u[i]].push_back(m + i);
}
to[A].push_back(2 * m + 1);
Mtx mt(m * 2 + 1,m * 2 + 1);
for(int i=1;i<=m * 2;i++){
for(int j : to[u[i]]){
mt.m[i][j] = 1;
}
if(i <= m) mt.m[i][i + m] = 0;
else mt.m[i][i - m] = 0;
}
Mtx ori(2 * m + 1,1);
ori.m[2 * m + 1][1] = 1;
Mtx resm = qmi(mt,t) * ori;
int res = 0;
for(int j : to[B]){
res = (res + resm.m[j][1]) % mod;
}
cout<<res;
return 0;
}

浙公网安备 33010602011771号