一道良好的矩阵乘法优化\(dp\)的题。

首先,一个比较\(naive\)的想法。

我们定义\(dp[i][j]\)表示已经走了\(i\)步,当前在点\(j\)的方案数。

由于题目中限制了不能立即走之前走过来的那个点,所以这个状态并不能优秀的转移。

尝试重新定义\(dp\)状态。

令\(dp[i][j]\)表示已经走了\(i\)步,当前在\(j\)这条边的终点的那个点。

假设\(to[j]=p\)

那么\(dp[i][j]\)可以转移到\(dp[i+1][out[p]] 其中\ (out[p]不为j的反向边)\)

其中\(out[p]\)表示p的出边(我们把题目中的每条无向拆成两个有向边)

最后求\(ans\)的时候,只需要枚举哪些边的终点是目标点,然后加起来即可

通过具体的边的限制,我们就能满足题目中的那个要求。

qwq但是我们发现,如果暴力转移的话,时间复杂度是不能够接受的。

考虑到每次只从\(i\)转移到\(i+1\)。

所以可以构造一个转移矩阵。

对于一个状态\(dp[x][i]\),然后在如果他能对编号为\(j\)的边产生贡献,那么我们把构造矩阵\(a[i][j]\)++

for (int i=1;i<=cnt;i++)
{
int to = y[i];
for (int j=0;j<out[to].size();j++)
{
int now = out[to][j];
if((i+1)==((now+1)^1)) continue;
b.a[i][now]++;
}
}

注意不能通过具体的点来判断,而要判断是否为反向边。

然后我们强行令初始矩阵为dp[1][i]的值,就是强行走一步,然后快速幂出来\(k-1\)次方的值,二者相乘,最后求解即可。

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define mk make_pair
#define pb push_back
#define ll long long
#define int long long using namespace std; inline int read()
{
int x=0,f=1;char ch=getchar();
while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
} const int maxn = 150;
const int maxm = 1e5+1e2;
const int mod = 45989; struct Ju{
int x,y;
int a[maxn][maxn];
Ju operator * (Ju b)
{
Ju ans;
memset(ans.a,0,sizeof(ans.a));
ans.x=x;
ans.y=b.y;
for (register int i=1;i<=ans.x;++i)
for (register int j=1;j<=ans.y;++j)
for (register int k=1;k<=y;++k)
ans.a[i][j]=(ans.a[i][j]+a[i][k]*b.a[k][j]%mod)%mod;
return ans;
}
}; Ju qsm(Ju i,int j)
{
Ju ans;
memset(ans.a,0,sizeof(ans.a));
ans.x=i.x;
ans.y=i.y;
for (int p=1;p<=i.x;p++) ans.a[p][p]=1;
while(j)
{
if (j&1) ans=ans*i;
i=i*i;
j>>=1;
}
return ans;
}; Ju a,b;
int n,m,k,s,t;
int x[maxm],y[maxm],w[maxm];
int cnt=0;
vector<int> in[maxn],out[maxn]; signed main()
{
n=read();m=read(),k=read(),s=read(),t=read();
s++;
t++;
for (int i=1;i<=m;i++)
{
int u=read(),v=read();
u++;
v++;
++cnt;
x[cnt]=u,y[cnt]=v;
++cnt;
x[cnt]=v,y[cnt]=u;
}
for (int i=1;i<=cnt;i++)
{
out[x[i]].pb(i);
in[y[i]].pb(i);
}
for (int i=1;i<=cnt;i++)
{
int to = y[i];
for (int j=0;j<out[to].size();j++)
{
int now = out[to][j];
if((i+1)==((now+1)^1)) continue;
b.a[i][now]++;
}
}
//for (int i=1;i<=cnt;i++)
// {
// for (int j=1;j<=cnt;j++) cout<<b.a[i][j]<<" ";
// cout<<endl;
//}
for (int i=0;i<out[s].size();i++)
{
a.a[1][out[s][i]]++;
//cout<<out[s][i]<<" "<<endl;
}
//cout<<"******************"<<endl;
//for (int i=1;i<=cnt;i++) cout<<a.a[1][i]<<" ";
//cout<<endl;
a.x=1;
a.y=cnt;
b.x=cnt;
b.y=cnt;
b=qsm(b,k-1);
a=a*b;
int ans = 0;
for (int i=1;i<=cnt;i++)
{
if (y[i]==t) ans=(ans+a.a[1][i])%mod;
//cout<<ans<<endl;
}
cout<<ans;
return 0;
}

洛谷2151[SDOI2009]HH去散步(dp+矩阵乘法优化)的更多相关文章

  1. BZOJ.1875.[SDOI2009]HH去散步(DP 矩阵乘法)

    题目链接 比较容易想到用f[i][j]表示走了i步后到达j点的方案数,但是题目要求不能走上一条走过的边 如果这样表示是不好转移的 可以考虑边,f[i][j]表示走了i步后到达第j条边的方案数,那么有 ...

  2. BZOJ_1875_[SDOI2009]HH去散步_矩阵乘法

    BZOJ_1875_[SDOI2009]HH去散步_矩阵乘法 Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但 是同时H ...

  3. BZOJ 1875: [SDOI2009]HH去散步( dp + 矩阵快速幂 )

    把双向边拆成2条单向边, 用边来转移...然后矩阵乘法+快速幂优化 ------------------------------------------------------------------ ...

  4. BZOJ-1875 HH去散步 DP+矩阵乘法快速幂

    1875: [SDOI2009]HH去散步 Time Limit: 20 Sec Memory Limit: 64 MB Submit: 1196 Solved: 553 [Submit][Statu ...

  5. 洛谷P2151 [SDOI2009] HH去散步 [矩阵加速]

    题目传送门 HH去散步 题目描述 HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走 ...

  6. [bzoj1875] [洛谷P2151] [SDOI2009] HH去散步

    Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但 是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回. 又 ...

  7. 1875. [SDOI2009]HH去散步【矩阵乘法】

    Description HH有个一成不变的习惯,喜欢饭后百步走.所谓百步走,就是散步,就是在一定的时间 内,走过一定的距离. 但 是同时HH又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回. 又 ...

  8. 洛谷 P2151 [SDOI2009]HH去散步

    题目链接 思路 如果没有不能走上一条边的限制,很显然就是dp. 设f[i][j]表示到达i点走了j步的方案数,移到k点可以表示为f[k][j+1]+=f[i][j]. 如果有限制的话,可以考虑用边表示 ...

  9. [bzoj1875][SDOI2009] HH去散步 [dp+矩阵快速幂]

    题面 传送门 正文 其实就是让你求有多少条长度为t的路径,但是有一个特殊条件:不能走过一条边以后又立刻反着走一次(如果两次经过同意条边中间隔了别的边是可以的) 如果没有这个特殊条件,我们很容易想到dp ...

随机推荐

  1. redis集群访问,重启,关闭,带密码访问集群

    安装ruby后查找如下文件  vi  进去后编辑 此处编写自己的密码,重启后便可带密码访问集群 随便选择一个节点输入如下指令查看集群信息 正常关闭redis命令如下: 重启redis集群再次以相同的命 ...

  2. Seq2Seq sequence-to-sequence模型 简介

    Sequence-to-sequence (seq2seq) 模型. 突破了传统的固定大小输入问题框架 开创了将DNN运用于翻译.聊天(问答)这类序列型任务的先河 并且在各主流语言之间的相互翻译,和语 ...

  3. MySQL读写IO的操作过程解析

    数据库作为存储系统,所有业务访问数据的操作都会转化为底层数据库系统的IO行为(缓存系统也可以当做是key-value的数据库),本文主要介绍访问MySQL数据库的IO流程以及IO相关的参数. 一.My ...

  4. CPU内部结构域寄存器

    CPU内部结构域寄存器   64位和32位系统区别: 寄存器是CPU内部最基本的存储单元. CPU对外是通过总线(地址.控制.数据)来和外部设备交互的,总线的宽度是8位,同时CPU的寄存器也是8位,那 ...

  5. 基于Linux系统的网络服务——高速缓存DNS及企业级域名解析服务

    1.DNS域名系统 DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数 ...

  6. 第17章-x86-64寄存器

    不同的CPU都能够解释的机器语言的体系称为指令集架构(ISA,Instruction Set Architecture),也可以称为指令集(instruction set).Intel将x86系列CP ...

  7. ubantu虚拟机搭建xl2tp服务

    在编译成功源码,安装完毕xl2tpd后,便可以配置xl2tpd服务.基本配置过程主要涉及两个配置文件:一个用来配置xl2tpd, 一个用来配置ppp协议.下面分别对这两个文件进行说明,最后添加xl2t ...

  8. JS009. 数组去重的多种方法总结与一步步优化

    两层for循环 这种函数的优点是兼容性好比较通用,缺点是时空复杂度都很直观的为O(n2),不利于维护和性能. var array = [1,1,'1','1'] function unique(arr ...

  9. centos7关于防火墙的一些操作

    防火墙相关 # 检查防火墙状态 systemctl status firewalld # 开启防火墙 systemctl start firewalld # 关闭防火墙 systemctl stop ...

  10. Sentry 后端监控 - 最佳实践(官方教程)

    系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...