P2151 HH去散步

题目
emm写这个题的题解不是因为它有多难,只是提醒一下自己要灵活一些……

这个题一眼矩阵乘法。但是——仔细点!题干要求不能走回头路(就是一条边不能连续走两次)。你会痛苦地发现,普通的矩阵快速幂是无法随时变换邻接矩阵的,所以这个题没法做所以你只能考虑别的方式。

这时候一个小trick——边点互换!这样子是不是就不会考虑重复了?另外,由于边是无向边,直接当点的话无法确定这条边是到哪个点的,故一条边要拆成两条来看,当作有向边。剩下的就是普通的矩阵加速dp了。

转移方程:

\(dp_{i, u} = \sum dp_{i-1, v}\)

其中 \(v\) 是所有与 \(u\) 相连的边。

最后注意一点点细节就好了

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
*/
posted @ 2023-06-07 10:55  霜木_Atomic  阅读(4)  评论(0编辑  收藏  举报