P2151 HH去散步
题目
emm写这个题的题解不是因为它有多难,只是提醒一下自己要灵活一些……
这个题一眼矩阵乘法。但是——仔细点!题干要求不能走回头路(就是一条边不能连续走两次)。你会痛苦地发现,普通的矩阵快速幂是无法随时变换邻接矩阵的,所以这个题没法做所以你只能考虑别的方式。
这时候一个小trick——边点互换!这样子是不是就不会考虑重复了?另外,由于边是无向边,直接当点的话无法确定这条边是到哪个点的,故一条边要拆成两条来看,当作有向边。剩下的就是普通的矩阵加速dp了。
转移方程:
其中
最后注意一点点细节就好了
Code:
#include<bits/stdc++.h> using namespace std; const int N = 130, mod = 45989; inline int read(){ int x = 0; char ch = getchar(); while(ch<'0'||ch>'9'){ ch = getchar(); } while(ch>='0'&&ch<='9'){ x = x*10+ch-48; ch = getchar(); } return x; } void add(int &a, int b){ a = (1ll*a+b)%mod; } int n, m, T, st, ed; int len; struct mat{ int f[N][N]; mat(){ memset(f, 0, sizeof(f)); } int * operator [](int x){return f[x];} void build(){ for(int i = 0; i<len; i++){ f[i][i] = 1; } } mat operator * (mat B){ mat t, BT; for(int i = 0; i<len; i++){ for(int j = 0; j<len;j++){ BT[i][j] = B[j][i]; } } for(int i = 0; i<len; i++){ for(int j = 0; j<len;j++){ for(int l = 0; l<len; l++){ add(t[i][j], (1ll*BT[j][l]*f[i][l])%mod); } } } return t; } void print(){ for(int i = 0; i<len; i++, puts("")){ for(int j = 0; j<len; j++){ printf("%d ", f[i][j]); } } } }; mat fpow(mat a, int b){ mat ret; ret.build(); while(b){ if(b&1){ ret = ret*a; } b>>=1; a = a*a; } return ret; } mat s, ans; vector<int> e[55][2];//0出1入 int idx; int main(){ n = read(), m = read(), T = read(), st = read(), ed = read(); len = m*2; for(int i = 1; i<=m; i++){ int u = read(), v = read(); e[u][0].push_back(i-1); e[u][1].push_back(i-1+m); e[v][1].push_back(i-1); e[v][0].push_back(i-1+m); } for(int i = 0; i<n; i++){//枚举点,当作边与边的桥梁(也就是新“边”) for(int j = 0; j<e[i][0].size(); j++){ for(int k = 0; k<e[i][1].size(); k++){ int ru = e[i][1][k]; int chu = e[i][0][j]; if(abs(ru-chu)!=m){//注意,一条边不能通向自己的反向边。 s[ru][chu]++; } } } } s = fpow(s, T-1); for(int i = 0; i<e[st][0].size(); i++){ int chu = e[st][0][i];//初始化:与起点相连的所有出边为起始边,注意这时候视作你已经走出一步。 ans[chu][chu]++; } ans = ans*s; int an = 0; for(int i = 0; i<e[st][0].size(); i++){ for(int j = 0; j<e[ed][1].size(); j++){ int chu = e[st][0][i]; int ru = e[ed][1][j]; add(an, ans[chu][ru]);//将与终点相连的所有入边作为终点边,答案就是从起始边出发到终止边的所有方案。 } } printf("%d\n", an); system("pause"); return 0; } /* 4 5 10 0 0 0 3 1 3 1 0 2 0 1 2 50 */