【SDOI2009】HH去散步

题目

HH有个一成不变的习惯,喜欢饭后百步走。所谓百步走,就是散步,就是在一定的时间
内,走过一定的距离。
但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回。
又因为HH是个喜欢变化的人,所以他每天走过的路径都不完全一样,他想知道他究竟有多
少种散步的方法。
现在给你学校的地图(假设每条路的长度都是一样的都是1),问长度为t,从给定地
点A走到给定地点B共有多少条符合条件的路径。

数据范围:
对于30%的数据,N ≤ 4,M ≤ 10,t ≤ 10。
对于100%的数据,N ≤ 20,M ≤ 60,t ≤ 2^30,0 ≤ A,B < N,0 ≤ Ai,Bi < N。

分析

这是我的第一道专题矩阵乘法训练的题目,感觉我挑错题了。。。
然而其实矩阵乘法的题目,一般可以先列出一般的转移方程,然后再改矩阵即可。
当然在列转移方程时一般都尽量把方程简单化。
所以这题的转移方程明显:
设f[i,j]表示第i秒走到第i条边的方案数。
为什么我们用边来列方程呢?
因为我们用边来列不仅可以知道走到的是哪个点,而且我们还可以知道
当前可不可以走到上一条边,转移会方便一点。
设b[j]表示第i条边走到哪里
st[i][0..120]表示第i个点为起点的边的编号
f[i+1,st[b[j]][1..st[b[j]][0]]+=f[i,j](且(i^1)!=st[b[j]][1..st[b[j]][0])
这样便可以转移了。

现在考虑如何把这个转移变成矩阵。
其实也比较简单,建立一个矩阵a,
其中a[i][j]表示的是边j连出去的点是第i条边的起点。
表示第j条边可以转移到第i条边去。

最终答案就是
bn矩阵*a矩阵快速幂t-1次方后的 bn[1][i] (i为可以连到终点的边)
bn矩阵也是一个1*2M的矩阵。bn[1,i]初始为1条件是i是起点可以连出去的边。

代码:

#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
using namespace std;
const int N=30,M=225,mo=45989;
struct node{
    int st,en;
}a[M];
int n,m,x,y,be,en,e[N][M],w[N][M];
long long t,ans;
typedef long long Matrix[M][M];
Matrix u,c,bn,an;
int main(){
    scanf("%d%d%lld%d%d",&n,&m,&t,&be,&en);
    be++;en++;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);x++;y++;
        e[x][++e[x][0]]=w[y][++w[y][0]]=2*i;
        e[y][++e[y][0]]=w[x][++w[x][0]]=2*i+1;
        a[2*i+1].st=y;a[2*i+1].en=x;
        a[2*i].st=x;a[2*i].en=y;
    }
    int op=m*2+1;
    for(int i=2;i<=op;i++)
       for(int j=2;j<=op;j++)
       if ((i!=(j^1))&&(a[j].en==a[i].st)) 
          an[j][i]++;
    for(int i=1;i<=e[be][0];i++) bn[1][e[be][i]]++;
    Matrix ret;
    for(int i=1;i<=op;i++)ret[i][i]=1;
    for(int i=1;i<=op;i++)
       for(int j=1;j<=op;j++) c[i][j]=an[i][j];
    t--;
    while (t){
        if (t&1){
            for(int i=1;i<=op;i++)
                for(int j=1;j<=op;j++){u[i][j]=0;
                    for(int k=1;k<=op;k++)
                     u[i][j]+=ret[i][k]*c[k][j];
            }
    for(int i=1;i<=op;i++)
       for(int j=1;j<=op;j++) ret[i][j]=u[i][j]%mo;
        }
        for(int i=1;i<=op;i++)  
           for(int j=1;j<=op;j++){ u[i][j]=0; 
               for(int k=1;k<=op;k++)
                  u[i][j]+=c[i][k]*c[k][j];
           }
        t=t/2;
        for(int i=1;i<=op;i++)
           for(int j=1;j<=op;j++)
                 c[i][j]=u[i][j]%mo;
    }
        for(int i=1;i<=op;i++) 
           for(int j=1;j<=op;j++){
               u[i][j]=0;
               for(int k=1;k<=op;k++)
                  u[i][j]+=bn[i][k]*ret[k][j];
           }
    for(int i=1;i<=w[en][0];i++) 
    ans+=u[1][w[en][i]],ans%=mo;
    printf("%d",ans);
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值