BZOJ1875 || 洛谷P2151 [SDOI2009]HH去散步【矩阵优化DP】

Time Limit: 20 Sec
Memory Limit: 64 MB

Description

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

Input

第一行:五个整数N,M,t,A,B。
N表示学校里的路口的个数
M表示学校里的 路的条数
t表示HH想要散步的距离
A表示散步的出发点
B则表示散步的终点。
接下来M行
每行一组Ai,Bi,表示从路口Ai到路口Bi有一条路。
数据保证Ai != Bi,但不保证任意两个路口之间至多只有一条路相连接。
路口编号从0到N -1。
同一行内所有数据均由一个空格隔开,行首行尾没有多余空格。没有多余空行。
答案模45989。
N ≤ 20,M ≤ 60,t ≤ 2^30,0 ≤ A,B

Output

一行,表示答案。


题目分析

不考虑 "不能走回头路"的限制
则可以用 d p [ i ] [ u ] dp[i][u] dp[i][u]表示
恰好在
i i i时刻走到 u u u的方案数
那么有转移方程 d p [ i ] [ u ] = ∑ d p [ i − 1 ] [ v ] dp[i][u]=\sum dp[i-1][v] dp[i][u]=dp[i1][v]其中v可以到达u

现在考虑如何排除回头路的影响
上述转移中前一步更新 d p [ i − 1 ] [ v ] dp[i-1][v] dp[i1][v]
其实相当于在寻找所有指向v的边,而这里面显然包含了 u → v u\rightarrow v uv
到更新 d p [ i ] [ u ] dp[i][u] dp[i][u]这一步时,显然不能有 u → v u\rightarrow v uv这条边参与

那么如何在保证 d p [ i − 1 ] [ v ] dp[i-1][v] dp[i1][v]正确的情况下令 d p [ i ] [ u ] dp[i][u] dp[i][u]的更新没有 u → v u\rightarrow v uv参与呢
我们把每条无向边拆成两条有向边
d p [ i ] [ j ] dp[i][j] dp[i][j]表示恰好在 i i i时刻走到 j j j指向的结点的方案数
在转移时只要剔除所有 j j j的反向边即可

满足线性递推的方程
矩阵快速幂优化即可


#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
typedef long long lt;

lt read()
{
    lt f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const lt mod=45989;
const int maxn=150;
int n,m,tim,s,t;
struct node{int v,nxt;}E[maxn];
int head[maxn],tot=1;

struct matrix
{
	lt mat[maxn][maxn],row,col;
	matrix(int r=0,int c=0){ 
		row=r; col=c; 
		for(int i=1;i<=row;++i)
		for(int j=1;j<=col;++j)
		mat[i][j]=0;
	}
};

matrix operator *(matrix a,matrix b){ 
	matrix c=matrix(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.mat[i][j]+=a.mat[i][k]*b.mat[k][j]%mod;
		c.mat[i][j]%=mod;
	}
	return c;
}

matrix qpow(matrix a,lt k)
{
	matrix res=matrix(a.row,a.col);
	for(int i=1;i<=a.row;++i) res.mat[i][i]=1;
	while(k){
		if(k&1) res=res*a;
		a=a*a; k>>=1;
	}
	return res;
}


void add(int u,int v)
{
	E[++tot].nxt=head[u];
	E[tot].v=v;
	head[u]=tot;
}

int main()
{
    n=read();m=read();tim=read();
    s=read()+1;t=read()+1;
     
    for(int i=1;i<=m;++i)
    {
        int u=read()+1,v=read()+1;
        add(u,v); add(v,u);
    }
     
    matrix h=matrix(tot-1,tot-1);
    for(int j=2;j<=tot;++j)
    {
        int u=E[j].v;
        for(int i=head[u];i;i=E[i].nxt)
        {
            if((i^1)==j) continue;
            h.mat[j-1][i-1]++;
        }
    }
    
    matrix f=matrix(1,tot-1);
    for(int i=head[s];i;i=E[i].nxt) 
    f.mat[1][i-1]++;
     
    matrix res=f*qpow(h,tim-1);
     
    lt ans=0;
    for(int i=head[t];i;i=E[i].nxt) 
    ans=(ans+res.mat[1][(i^1)-1])%mod;
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值